Enable popup clamping to container (#7898)

* Initial commit

* Show demo tiddler by default

* Correct filename for example tiddler

*sigh*

* Distinguish right/bottom/both/none

* Fix typo

* Revert DefaultTiddlers.tid to original version

* Add changenote

* Fix ESLint errors

* Update documentation tiddlers
This commit is contained in:
yaisog
2025-12-19 09:44:03 +01:00
committed by GitHub
parent 2b6bdcbf97
commit fc1e53a777
4 changed files with 149 additions and 32 deletions

View File

@@ -89,13 +89,33 @@ RevealWidget.prototype.positionPopup = function(domNode) {
top = this.popup.top + this.popup.height;
break;
}
// if requested, clamp the popup so that it will always be fully inside its parent (the first upstream element with position:relative), as long as the popup is smaller than its parent
// if position is absolute then clamping is done to the canvas boundary, since there is no "parent"
if(this.clampToParent !== "none") {
if(this.popup.absolute) {
var parentWidth = window.innerWidth,
parentHeight = window.innerHeight;
} else {
var parentWidth = domNode.offsetParent.offsetWidth,
parentHeight = domNode.offsetParent.offsetHeight;
}
var right = left + domNode.offsetWidth,
bottom = top + domNode.offsetHeight;
if((this.clampToParent === "both" || this.clampToParent === "right") && right > parentWidth) {
left = parentWidth - domNode.offsetWidth;
}
if((this.clampToParent === "both" || this.clampToParent === "bottom") && bottom > parentHeight) {
top = parentHeight - domNode.offsetHeight;
}
// clamping on left and top sides is taken care of by positionAllowNegative
}
if(!this.positionAllowNegative) {
left = Math.max(0,left);
top = Math.max(0,top);
}
if (this.popup.absolute) {
if(this.popup.absolute) {
// Traverse the offsetParent chain and correct the offset to make it relative to the parent node.
for (var offsetParentDomNode = domNode.offsetParent; offsetParentDomNode; offsetParentDomNode = offsetParentDomNode.offsetParent) {
for(var offsetParentDomNode = domNode.offsetParent; offsetParentDomNode; offsetParentDomNode = offsetParentDomNode.offsetParent) {
left -= offsetParentDomNode.offsetLeft;
top -= offsetParentDomNode.offsetTop;
}
@@ -123,6 +143,7 @@ RevealWidget.prototype.execute = function() {
this.openAnimation = this.animate === "no" ? undefined : "open";
this.closeAnimation = this.animate === "no" ? undefined : "close";
this.updatePopupPosition = this.getAttribute("updatePopupPosition","no") === "yes";
this.clampToParent = this.getAttribute("clamp","none");
// Compute the title of the state tiddler and read it
this.stateTiddlerTitle = this.state;
this.stateTitle = this.getAttribute("stateTitle");
@@ -141,7 +162,7 @@ Read the state tiddler
RevealWidget.prototype.readState = function() {
// Read the information from the state tiddler
var state,
defaultState = this["default"];
defaultState = this["default"];
if(this.stateTitle) {
var stateTitleTiddler = this.wiki.getTiddler(this.stateTitle);
if(this.stateField) {
@@ -263,7 +284,7 @@ RevealWidget.prototype.updateState = function() {
} else {
$tw.anim.perform(this.closeAnimation,domNode,{callback: function() {
//make sure that the state hasn't changed during the close animation
self.readState()
self.readState();
if(!self.isOpen) {
domNode.setAttribute("hidden","true");
}

View File

@@ -0,0 +1,11 @@
title: $:/changenotes/5.4.0/#7898
description: Add clamp attribute to RevealWidget
release: 5.4.0
tags: $:/tags/ChangeNote
change-type: enhancement
change-category: widget
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/7898
github-contributors: yaisog
The attribute `clamp` is added to the RevealWidget to force the popup to be displayed inside its container without overflowing. The value can be `right`, `bottom` or `both`.
Clamping to left and top can be accomplished via the `positionAllowNegative` attribute.

View File

@@ -1,49 +1,66 @@
caption: reveal
created: 20131024141900000
jeremy: tiddlywiki
modified: 20250211091937860
modified: 20251212091659847
tags: Widgets
title: RevealWidget
type: text/vnd.tiddlywiki
! Introduction
The reveal widget hides or shows its content depending upon the value of a [[state tiddler|StateTiddler]]. The type of the widget determines the condition for the content being displayed:
* type=''match'': the content is displayed if the state tiddler matches the text attribute value
* type=''nomatch'': the content is displayed if the state tiddler doesn't match the text attribute value
* type=''popup'': the content is displayed as a popup as described in the PopupMechanism
* type=''lt'': the content is displayed if the state tiddler contains an integer with a value ''less than'' the text attribute value
* type=''gt'': the content is displayed if the state tiddler contains an integer with a value ''greater than'' the text attribute value
* type=''lteq'': the content is displayed if the state tiddler contains an integer with a value ''less than or equal to'' the text attribute value
* type=''gteq'': the content is displayed if the state tiddler contains an integer with a value ''greater than or equal to'' the text attribute value
The reveal widget hides or shows its content depending upon the value of a [[state tiddler|StateMechanism]].
! Content and Attributes
The content of the `<$reveal>` widget is displayed according to the rules given above.
|!Attribute |!Description |
|state |A TextReference containing the state |
|stateTitle |A title containing the state, ''without'' TextReference. Gets preferred over the <<.attr state>> attribute |
|stateField |A ''field name'' which is used to look for the state, if the attribute <<.attr stateTitle>> is present |
|stateIndex |An ''index'' which is used to look for the state, if the attribute <<.attr stateTitle>> is present |
|tag |Overrides the default [[HTML Tags]] (`<div>` in block mode or `<span>` in inline mode) |
|type |The type of matching performed: ''match'', ''nomatch'', ''popup'', ''lt'', ''gt'', ''lteq'' or ''gteq'' |
|text |The text to match when the type is ''match'', ''nomatch'', ''lt'', ''gt'', ''lteq'' or ''gteq'' |
|class |An optional CSS class name to be assigned to the HTML element<br/>&raquo; Set to `tc-popup-keep` to make a popup "sticky", so it won't close when you click inside of it|
|style |An optional CSS style attribute to be assigned to the HTML element |
|position |The position used for the popup when the type is ''popup''. Can be ''left'', ''above'', ''aboveleft'', ''aboveright'', ''right'', ''belowleft'', ''belowright'' or ''below'' |
|positionAllowNegative |Set to "yes" to prevent computed popup positions from being clamped to be above zero |
|default |Default value to use when the state tiddler is missing |
|animate |Set to "yes" to animate opening and closure (defaults to "no"; requires "retain" to be set to "yes") |
|retain |Set to "yes" to force the content to be retained even when hidden (defaults to "no") |
|updatePopupPosition|<<.from-version "5.1.23">>Set to "yes" to update the popup position when the state tiddler is updated (defaults to "no")|
|<<.attr state>> |A TextReference containing the state |
|<<.attr stateTitle>> |A title containing the state, without TextReference. Gets preferred over the <<.attr state>> attribute if both are set |
|<<.attr stateField>> |A field name which is used to look for the state, if the attribute <<.attr stateTitle>> is present |
|<<.attr stateIndex>> |An index which is used to look for the state, if the attribute <<.attr stateTitle>> is present |
|<<.attr default>> |Default value to use when the state tiddler is missing |
|<<.attr tag>> |Overrides the default [[HTML tag|HTML Tags]] (`<div>` in block mode or `<span>` in inline mode) |
|<<.attr type>> |The type of matching performed, see below |
|<<.attr text>> |The text to match when the type is <<.value match>>, <<.value nomatch>>, <<.value lt>>, <<.value gt>>, <<.value lteq>> or <<.value gteq>> |
|<<.attr class>> |An optional CSS class name to be assigned to the HTML element|
|<<.attr style>> |An optional CSS style attribute to be assigned to the HTML element |
|<<.attr position>> |The position used for the popup when the type is <<.value popup>>.<br> Can be <<.value left>>, <<.value above>>, <<.value aboveleft>>, <<.value aboveright>>, <<.value right>>, <<.value belowleft>>, <<.value belowright>> or <<.value below>>. Also see [[Popup Clamping Example|RevealWidget (Popup Clamping Example)]] |
|<<.attr positionAllowNegative>> |Set to <<.value yes>> to allow computed popup positions to be negative relative to their container or the document window (for absolutely positioned popups). Defaults to <<.value no>> |
|<<.attr clamp>> |Set to <<.value right>>, <<.value bottom>> or <<.value both>> to prevent a popup to overflow its container, see below |
|<<.attr animate>> |Set to <<.value yes>> to animate opening and closing. Defaults to <<.value no>> |
|<<.attr retain>> |Set to <<.value yes>> to force the content to be retained even when hidden. Defaults to <<.value no>> |
|<<.attr updatePopupPosition>> |<<.from-version "5.1.23">>Set to <<.value yes>> to update the popup position when the state tiddler is updated. Defaults to <<.value no>> |
<<.tip """<$macrocall $name=".from-version" version="5.1.18"/> <$macrocall $name=".attr" _="stateTitle"/>, <$macrocall $name=".attr" _="stateField"/> and <$macrocall $name=".attr" _="stateIndex"/> attributes allow specifying Tiddler states ''directly'', without interpreting them as [[TextReferences|TextReference]].
<<.tip """<$macrocall $name=".from-version" version="5.1.18"/> <<.attr stateTitle>>, <<.attr stateField>> and <<.attr stateIndex>> attributes allow specifying Tiddler states directly, without interpreting them as [[TextReferences|TextReference]].
This is useful for edge-cases where titles may contain characters that are used to denote Tiddler fields or indices (`!!`, `##`)""">>
<<.tip """Retaining the content when hidden can give poor performance since the hidden content requires refresh processing even though it is not displayed. On the other hand, the content can be revealed much more quickly. Note that setting ''animate="yes"'' will also require ''retain="yes"''""">>
<<.tip """Retaining the content when hidden can give poor performance since the hidden content requires refresh processing even though it is not displayed. On the other hand, the content can be revealed much more quickly. Note that setting `animate="yes"` will also require `retain="yes"`""">>
<<.tip """Set the <<.attr class>> attribute to <<.value tc-popup-keep>> to make a popup "sticky" so it will not close when you click inside of it""">>
!! <<.attr type>> Attribute
The <<.attr type>> of the widget determines the condition for the content being displayed:
* <<.value match>>: the content is displayed if the state tiddler matches the text attribute value
* <<.value nomatch>>: the content is displayed if the state tiddler doesn't match the text attribute value
* <<.value popup>>: the content is displayed as a popup as described in the PopupMechanism
* <<.value lt>>: the content is displayed if the state tiddler contains an integer with a value ''less than'' the text attribute value
* <<.value gt>>: the content is displayed if the state tiddler contains an integer with a value ''greater than'' the text attribute value
* <<.value lteq>>: the content is displayed if the state tiddler contains an integer with a value ''less than or equal to'' the text attribute value
* <<.value gteq>>: the content is displayed if the state tiddler contains an integer with a value ''greater than or equal to'' the text attribute value
!! Popup Clamping
Popups can be forced not to overflow their container (when relatively positioned) or the document window (when absolutely positioned). The popup's ''container'' is the nearest ancestor element with CSS positioning (<<.attr position>>: <<.value relative>>, <<.value absolute>>, <<.value fixed>>, or <<.value sticky>>).
Overflow to the left or top is prevented by setting <<.attr positionAllowNegative>> to <<.value no>> (the default). Clamping to the right and bottom is achieved by setting <<.attr clamp>> to <<.value right>>, <<.value bottom>> or <<.value both>>.
See [[Popup Clamping Example|RevealWidget (Popup Clamping Example)]]
<<.tip """Refer to [[ButtonWidget]] and [[Coordinate Systems]] for information on relative and absolute positioning""">>
! Examples

View File

@@ -0,0 +1,68 @@
created: 20231218192649874
modified: 20251212092210168
tags: RevealWidget
title: RevealWidget (Popup Clamping Example)
<style>
.container {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
width: 600px;
height: 500px;
overflow: hidden;
background-color: #F8F8F8;
position: relative;
border: 1px solid #888;
}
.grid-item {
justify-self: center;
align-self: center;
}
.grid-popup .tc-drop-down {
padding: 1em;
min-width: 15em;
}
</style>
This example demonstrates the use of <<.attr positionAllowNegative>> and <<.attr clamp>> to clamp a popup inside its parent container.
<<.attr clamp>>=<$select tiddler="$:/temp/clamp-demo-active" field="text" default="none">
<option value="none">none</option>
<option value="right">right</option>
<option value="bottom">bottom</option>
<option value="both">both</option>
</$select>
<$checkbox tiddler="$:/temp/clamp-demo-allow-negative" field="text" checked="yes" unchecked="no" default="no"> <<.attr positionAllowNegative>></$checkbox>
<<.attr position>>=<$select tiddler="$:/temp/clamp-demo-position" field="text" default="below">
<option value="left">left</option>
<option value="above">above</option>
<option value="aboveleft">aboveleft</option>
<option value="aboveright">aboveright</option>
<option value="right">right</option>
<option value="belowleft">belowleft</option>
<option value="belowright">belowright</option>
<option value="below">below</option>
</$select>
<div class="container">
<$list filter="[range[25]]">
<div class="grid-item">
<$button popup="$:/state/popup-clamping">Pop Me Up</$button>
</div>
</$list>
<$reveal type="popup"
position={{{ [{$:/temp/clamp-demo-position}else[below]] }}}
state="$:/state/popup-clamping"
class="grid-popup"
clamp={{{ [{$:/temp/clamp-demo-active}else[none]] }}}
positionAllowNegative={{{ [{$:/temp/clamp-demo-allow-negative}else[no]] }}}>
<div class="tc-drop-down">
!! This is the popup
And this is some text
</div>
</$reveal>
</div>