@stackoverflow/stacks 0.74.0 → 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/index.d.ts +7 -0
- package/dist/controllers/s-expandable-control.d.ts +17 -0
- package/dist/controllers/s-modal.d.ts +97 -0
- package/dist/controllers/s-navigation-tablist.d.ts +36 -0
- package/dist/controllers/s-popover.d.ts +155 -0
- package/dist/controllers/s-table.d.ts +8 -0
- package/dist/controllers/s-tooltip.d.ts +82 -0
- package/dist/controllers/s-uploader.d.ts +48 -0
- package/dist/css/stacks.css +4633 -2703
- package/dist/css/stacks.min.css +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/js/stacks.js +6026 -5403
- package/dist/js/stacks.min.js +1 -1
- package/dist/stacks.d.ts +21 -0
- package/lib/css/atomic/{_stacks-borders.less → borders.less} +30 -30
- package/lib/css/atomic/{_stacks-colors.less → colors.less} +0 -0
- package/lib/css/atomic/{_stacks-flex.less → flex.less} +14 -13
- package/lib/css/atomic/{_stacks-grid.less → grid.less} +12 -11
- package/lib/css/atomic/{_stacks-misc.less → misc.less} +23 -22
- package/lib/css/atomic/spacing.less +314 -0
- package/lib/css/atomic/{_stacks-typography.less → typography.less} +29 -29
- package/lib/css/atomic/width-height.less +194 -0
- package/lib/css/base/{_stacks-body.less → body.less} +3 -5
- package/lib/css/base/{_stacks-configuration-static.less → configuration-static.less} +3 -1
- package/lib/css/base/{_stacks-icons.less → icons.less} +0 -0
- package/lib/css/base/{_stacks-internals.less → internals.less} +0 -10
- package/lib/css/base/{_stacks-reset-meyer.less → reset-meyer.less} +0 -0
- package/lib/css/base/{_stacks-reset-normalize.less → reset-normalize.less} +0 -0
- package/lib/css/base/{_stacks-reset.less → reset.less} +2 -2
- package/lib/css/components/{_stacks-activity-indicator.less → activity-indicator.less} +9 -9
- package/lib/css/components/{_stacks-avatars.less → avatars.less} +40 -40
- package/lib/css/components/{_stacks-badges.less → badges.less} +11 -11
- package/lib/css/components/{_stacks-banners.less → banners.less} +5 -6
- package/lib/css/components/{_stacks-blank-states.less → blank-states.less} +2 -2
- package/lib/css/components/{_stacks-breadcrumbs.less → breadcrumbs.less} +7 -7
- package/lib/css/components/{_stacks-button-groups.less → button-groups.less} +2 -2
- package/lib/css/components/{_stacks-buttons.less → buttons.less} +77 -72
- package/lib/css/components/{_stacks-cards.less → cards.less} +2 -2
- package/lib/css/components/{_stacks-code-blocks.less → code-blocks.less} +8 -8
- package/lib/css/components/{_stacks-collapsible.less → collapsible.less} +0 -0
- package/lib/css/components/{_stacks-inputs.less → inputs.less} +41 -41
- package/lib/css/components/{_stacks-link-previews.less → link-previews.less} +17 -17
- package/lib/css/components/{_stacks-links.less → links.less} +4 -4
- package/lib/css/components/{_stacks-menu.less → menu.less} +5 -5
- package/lib/css/components/{_stacks-modals.less → modals.less} +20 -20
- package/lib/css/components/{_stacks-navigation.less → navigation.less} +12 -12
- package/lib/css/components/{_stacks-notices.less → notices.less} +12 -12
- package/lib/css/components/{_stacks-page-titles.less → page-titles.less} +9 -9
- package/lib/css/components/{_stacks-pagination.less → pagination.less} +8 -8
- package/lib/css/components/{_stacks-popovers.less → popovers.less} +17 -17
- package/lib/css/components/{_stacks-post-summary.less → post-summary.less} +155 -60
- package/lib/css/components/{_stacks-progress-bars.less → progress-bars.less} +29 -30
- package/lib/css/components/{_stacks-prose.less → prose.less} +31 -31
- package/lib/css/components/{_stacks-spinner.less → spinner.less} +14 -14
- package/lib/css/components/{_stacks-tables.less → tables.less} +10 -10
- package/lib/css/components/{_stacks-tags.less → tags.less} +33 -41
- package/lib/css/components/{_stacks-toggle-switches.less → toggle-switches.less} +5 -5
- package/lib/css/components/{_stacks-topbar.less → topbar.less} +27 -40
- package/lib/css/components/{_stacks-uploader.less → uploader.less} +18 -18
- package/lib/css/components/{_stacks-user-cards.less → user-cards.less} +14 -14
- package/lib/css/components/{_stacks-widget-dynamic.less → widget-dynamic.less} +1 -1
- package/lib/css/components/{_stacks-widget-static.less → widget-static.less} +39 -38
- package/lib/css/exports/{_stacks-constants-colors.less → constants-colors.less} +21 -29
- package/lib/css/exports/constants-helpers.less +108 -0
- package/lib/css/exports/constants-type.less +153 -0
- package/lib/css/exports/{_stacks-exports.less → exports.less} +4 -4
- package/lib/css/exports/{_stacks-mixins.less → mixins.less} +18 -1
- package/lib/css/stacks-dynamic.less +12 -13
- package/lib/css/stacks-static.less +38 -38
- package/lib/ts/controllers/index.ts +8 -0
- package/lib/ts/controllers/s-expandable-control.ts +163 -164
- package/lib/ts/controllers/s-modal.ts +259 -261
- package/lib/ts/controllers/s-navigation-tablist.ts +96 -97
- package/lib/ts/controllers/s-popover.ts +438 -440
- package/lib/ts/controllers/s-table.ts +203 -203
- package/lib/ts/controllers/s-tooltip.ts +195 -196
- package/lib/ts/controllers/s-uploader.ts +162 -164
- package/lib/ts/index.ts +20 -0
- package/lib/ts/stacks.ts +73 -68
- package/lib/tsconfig.json +8 -6
- package/package.json +43 -27
- package/dist/css/stacks-flexgrid-shim.min.css +0 -1
- package/lib/css/atomic/_stacks-spacing.less +0 -162
- package/lib/css/atomic/_stacks-width-height.less +0 -189
- package/lib/css/base/_stacks-configuration-dynamic.less +0 -106
- package/lib/css/exports/_stacks-constants-helpers.less +0 -139
- package/lib/css/exports/_stacks-constants-type.less +0 -91
- package/lib/ts/finalize.ts +0 -1
- package/lib/ts/stimulus.d.ts +0 -4
|
@@ -1,549 +1,547 @@
|
|
|
1
|
-
|
|
1
|
+
import { createPopper, Placement } from '@popperjs/core';
|
|
2
|
+
import * as Stacks from "../stacks";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
type OutsideClickBehavior = "always" | "never" | "if-in-viewport" | "after-dismissal";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
export abstract class BasePopoverController extends Stacks.StacksController {
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
private popper!: Popper;
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
protected popoverElement!: HTMLElement;
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
protected referenceElement!: HTMLElement;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
/**
|
|
15
|
+
* An attribute containing the ID of the popover element to render, e.g. aria-controls or aria-describedby.
|
|
16
|
+
*/
|
|
17
|
+
protected abstract popoverSelectorAttribute: string;
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Binds events to the document on element show
|
|
21
|
+
*/
|
|
22
|
+
protected abstract bindDocumentEvents(): void;
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Unbinds events on the document on element hide
|
|
26
|
+
*/
|
|
27
|
+
protected abstract unbindDocumentEvents(): void;
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Returns true if the if the popover is currently visible.
|
|
31
|
+
*/
|
|
32
|
+
get isVisible() {
|
|
33
|
+
const popoverElement = this.popoverElement;
|
|
34
|
+
return popoverElement ? popoverElement.classList.contains("is-visible") : false;
|
|
35
|
+
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Gets whether the element is visible in the browser's viewport.
|
|
39
|
+
*/
|
|
40
|
+
get isInViewport() {
|
|
41
|
+
const element = this.popoverElement;
|
|
42
|
+
if (!this.isVisible || !element) { return false; }
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// From https://stackoverflow.com/a/5354536. Theoretically, this could be calculated using Popper's detectOverflow function,
|
|
45
|
+
// but it's unclear how to access that with our current configuration.
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const rect = element.getBoundingClientRect();
|
|
48
|
+
const viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
|
|
49
|
+
const viewWidth = Math.max(document.documentElement.clientWidth, window.innerWidth);
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
return rect.bottom > 0 && rect.top < viewHeight && rect.right > 0 && rect.left < viewWidth;
|
|
52
|
+
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
54
|
+
protected get shouldHideOnOutsideClick() {
|
|
55
|
+
const hideBehavior = <OutsideClickBehavior>this.data.get("hide-on-outside-click");
|
|
56
|
+
switch (hideBehavior) {
|
|
57
|
+
case "after-dismissal":
|
|
58
|
+
case "never":
|
|
59
|
+
return false;
|
|
60
|
+
case "if-in-viewport":
|
|
61
|
+
return this.isInViewport;
|
|
62
|
+
default:
|
|
63
|
+
return true;
|
|
64
64
|
}
|
|
65
|
+
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
this.data.delete("auto-show");
|
|
67
|
+
/**
|
|
68
|
+
* Initializes and validates controller variables
|
|
69
|
+
*/
|
|
70
|
+
connect() {
|
|
71
|
+
super.connect();
|
|
72
|
+
this.validate();
|
|
73
|
+
if (this.isVisible) {
|
|
74
|
+
// just call initialize here, not show. This keeps already visible popovers from adding/firing document events
|
|
75
|
+
this.initializePopper();
|
|
76
|
+
} else if (this.data.get("auto-show") === "true") {
|
|
77
|
+
this.show(null);
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*/
|
|
85
|
-
disconnect() {
|
|
86
|
-
this.hide();
|
|
87
|
-
if (this.popper) {
|
|
88
|
-
this.popper.destroy();
|
|
89
|
-
delete this.popper;
|
|
90
|
-
}
|
|
91
|
-
super.disconnect();
|
|
92
|
-
}
|
|
80
|
+
this.data.delete("auto-show");
|
|
81
|
+
}
|
|
93
82
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Cleans up popper.js elements and disconnects all added event listeners
|
|
85
|
+
*/
|
|
86
|
+
disconnect() {
|
|
87
|
+
this.hide();
|
|
88
|
+
if (this.popper) {
|
|
89
|
+
this.popper.destroy();
|
|
90
|
+
delete this.popper;
|
|
99
91
|
}
|
|
92
|
+
super.disconnect();
|
|
93
|
+
}
|
|
100
94
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let dispatcherElement = this.getDispatcher(dispatcher);
|
|
108
|
-
|
|
109
|
-
if (this.triggerEvent("show", {
|
|
110
|
-
dispatcher: dispatcherElement
|
|
111
|
-
}).defaultPrevented) { return; }
|
|
95
|
+
/**
|
|
96
|
+
* Toggles the visibility of the popover
|
|
97
|
+
*/
|
|
98
|
+
toggle(dispatcher: Event|Element|null = null) {
|
|
99
|
+
this.isVisible ? this.hide(dispatcher) : this.show(dispatcher);
|
|
100
|
+
}
|
|
112
101
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Shows the popover if not already visible
|
|
104
|
+
*/
|
|
105
|
+
show(dispatcher: Event|Element|null = null) {
|
|
106
|
+
if (this.isVisible) { return; }
|
|
116
107
|
|
|
117
|
-
|
|
108
|
+
let dispatcherElement = this.getDispatcher(dispatcher);
|
|
118
109
|
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
if (this.triggerEvent("show", {
|
|
111
|
+
dispatcher: dispatcherElement
|
|
112
|
+
}).defaultPrevented) { return; }
|
|
121
113
|
|
|
122
|
-
|
|
114
|
+
if (!this.popper) {
|
|
115
|
+
this.initializePopper();
|
|
123
116
|
}
|
|
124
117
|
|
|
125
|
-
|
|
126
|
-
* Hides the popover if not already hidden
|
|
127
|
-
*/
|
|
128
|
-
hide(dispatcher: Event|Element|null = null) {
|
|
129
|
-
if (!this.isVisible) { return; }
|
|
118
|
+
this.popoverElement!.classList.add("is-visible");
|
|
130
119
|
|
|
131
|
-
|
|
120
|
+
// ensure the popper has been positioned correctly
|
|
121
|
+
this.scheduleUpdate();
|
|
132
122
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}).defaultPrevented) { return; }
|
|
136
|
-
|
|
137
|
-
this.popoverElement.classList.remove("is-visible");
|
|
123
|
+
this.shown(dispatcherElement);
|
|
124
|
+
}
|
|
138
125
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Hides the popover if not already hidden
|
|
128
|
+
*/
|
|
129
|
+
hide(dispatcher: Event|Element|null = null) {
|
|
130
|
+
if (!this.isVisible) { return; }
|
|
144
131
|
|
|
145
|
-
|
|
146
|
-
if (<OutsideClickBehavior>this.data.get("hide-on-outside-click") === "after-dismissal") {
|
|
147
|
-
this.data.delete("hide-on-outside-click");
|
|
148
|
-
}
|
|
132
|
+
let dispatcherElement = this.getDispatcher(dispatcher);
|
|
149
133
|
|
|
150
|
-
|
|
151
|
-
|
|
134
|
+
if (this.triggerEvent("hide", {
|
|
135
|
+
dispatcher: dispatcherElement
|
|
136
|
+
}).defaultPrevented) { return; }
|
|
152
137
|
|
|
153
|
-
|
|
154
|
-
* Binds document events for this popover and fires the shown event
|
|
155
|
-
*/
|
|
156
|
-
protected shown(dispatcher: Element|null = null) {
|
|
157
|
-
this.bindDocumentEvents();
|
|
158
|
-
this.triggerEvent("shown", {
|
|
159
|
-
dispatcher: dispatcher
|
|
160
|
-
});
|
|
161
|
-
}
|
|
138
|
+
this.popoverElement.classList.remove("is-visible");
|
|
162
139
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.unbindDocumentEvents();
|
|
168
|
-
this.triggerEvent("hidden", {
|
|
169
|
-
dispatcher: dispatcher
|
|
170
|
-
});
|
|
140
|
+
if (this.popper) {
|
|
141
|
+
// completely destroy the popper on hide; this is in line with Popper.js's performance recommendations
|
|
142
|
+
this.popper.destroy();
|
|
143
|
+
delete this.popper;
|
|
171
144
|
}
|
|
172
145
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
protected generatePopover(): HTMLElement | null {
|
|
177
|
-
return null;
|
|
146
|
+
// on first interaction, hide-on-outside-click with value "after-dismissal" reverts to the default behavior
|
|
147
|
+
if (<OutsideClickBehavior>this.data.get("hide-on-outside-click") === "after-dismissal") {
|
|
148
|
+
this.data.delete("hide-on-outside-click");
|
|
178
149
|
}
|
|
179
150
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
*/
|
|
183
|
-
private initializePopper() {
|
|
184
|
-
// @ts-ignore
|
|
185
|
-
this.popper = Popper.createPopper(this.referenceElement, this.popoverElement, {
|
|
186
|
-
placement: this.data.get("placement") || "bottom",
|
|
187
|
-
modifiers: [
|
|
188
|
-
{
|
|
189
|
-
name: "offset",
|
|
190
|
-
options: {
|
|
191
|
-
offset: [0, 10], // The entire popover should be 10px away from the element
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
name: "arrow",
|
|
196
|
-
options: {
|
|
197
|
-
element: ".s-popover--arrow"
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
]
|
|
201
|
-
});
|
|
202
|
-
}
|
|
151
|
+
this.hidden(dispatcherElement);
|
|
152
|
+
}
|
|
203
153
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Binds document events for this popover and fires the shown event
|
|
156
|
+
*/
|
|
157
|
+
protected shown(dispatcher: Element|null = null) {
|
|
158
|
+
this.bindDocumentEvents();
|
|
159
|
+
this.triggerEvent("shown", {
|
|
160
|
+
dispatcher: dispatcher
|
|
161
|
+
});
|
|
162
|
+
}
|
|
209
163
|
|
|
210
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Unbinds document events for this popover and fires the hidden event
|
|
166
|
+
*/
|
|
167
|
+
protected hidden(dispatcher: Element|null = null) {
|
|
168
|
+
this.unbindDocumentEvents();
|
|
169
|
+
this.triggerEvent("hidden", {
|
|
170
|
+
dispatcher: dispatcher
|
|
171
|
+
});
|
|
172
|
+
}
|
|
211
173
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Generates the popover if not found during initialization
|
|
176
|
+
*/
|
|
177
|
+
protected generatePopover(): HTMLElement | null {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
215
180
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Initializes the Popper for this instance
|
|
183
|
+
*/
|
|
184
|
+
private initializePopper() {
|
|
185
|
+
// @ts-ignore
|
|
186
|
+
this.popper = createPopper(this.referenceElement, this.popoverElement, {
|
|
187
|
+
placement: this.data.get("placement") as Placement || "bottom",
|
|
188
|
+
modifiers: [
|
|
189
|
+
{
|
|
190
|
+
name: "offset",
|
|
191
|
+
options: {
|
|
192
|
+
offset: [0, 10], // The entire popover should be 10px away from the element
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "arrow",
|
|
197
|
+
options: {
|
|
198
|
+
element: ".s-popover--arrow"
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
]
|
|
202
|
+
});
|
|
203
|
+
}
|
|
220
204
|
|
|
221
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Validates the popover settings and attempts to set necessary internal variables
|
|
207
|
+
*/
|
|
208
|
+
private validate() {
|
|
209
|
+
var referenceSelector = this.data.get("reference-selector");
|
|
222
210
|
|
|
223
|
-
|
|
211
|
+
this.referenceElement = <HTMLElement>this.element;
|
|
224
212
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
213
|
+
// if there is an alternative reference selector and that element exists, use it (and throw if it isn't found)
|
|
214
|
+
if (referenceSelector) {
|
|
215
|
+
this.referenceElement = <HTMLElement>this.element.querySelector(referenceSelector);
|
|
228
216
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
// if the popover isn't named, attempt to generate it
|
|
234
|
-
else {
|
|
235
|
-
popoverElement = this.generatePopover();
|
|
217
|
+
if (!this.referenceElement) {
|
|
218
|
+
throw "Unable to find element by reference selector: " + referenceSelector;
|
|
236
219
|
}
|
|
220
|
+
}
|
|
237
221
|
|
|
238
|
-
|
|
239
|
-
throw "unable to find or generate popover element";
|
|
240
|
-
}
|
|
222
|
+
const popoverId = this.referenceElement.getAttribute(this.popoverSelectorAttribute);
|
|
241
223
|
|
|
242
|
-
|
|
243
|
-
}
|
|
224
|
+
var popoverElement = null;
|
|
244
225
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
*/
|
|
249
|
-
protected getDispatcher(dispatcher: Event|Element|null = null) : Element {
|
|
250
|
-
if (dispatcher instanceof Event) {
|
|
251
|
-
return <Element>dispatcher.target;
|
|
252
|
-
}
|
|
253
|
-
else if (dispatcher instanceof Element) {
|
|
254
|
-
return dispatcher;
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
return this.element;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
226
|
+
// if the popover is named, attempt to fetch it (and throw an error if it doesn't exist)
|
|
227
|
+
if (popoverId) {
|
|
228
|
+
popoverElement = document.getElementById(popoverId);
|
|
260
229
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
*/
|
|
264
|
-
protected scheduleUpdate() {
|
|
265
|
-
if (this.popper && this.isVisible) {
|
|
266
|
-
this.popper.update();
|
|
230
|
+
if (!popoverElement){
|
|
231
|
+
throw `[${this.popoverSelectorAttribute}="{POPOVER_ID}"] required`;
|
|
267
232
|
}
|
|
268
233
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
static targets = [];
|
|
273
|
-
|
|
274
|
-
protected popoverSelectorAttribute = "aria-controls";
|
|
275
|
-
|
|
276
|
-
private boundHideOnOutsideClick!: any;
|
|
277
|
-
private boundHideOnEscapePress!: any;
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Toggles optional classes in addition to BasePopoverController.shown
|
|
281
|
-
*/
|
|
282
|
-
protected shown(dispatcher: Element|null = null) {
|
|
283
|
-
this.toggleOptionalClasses(true);
|
|
284
|
-
super.shown(dispatcher);
|
|
234
|
+
// if the popover isn't named, attempt to generate it
|
|
235
|
+
else {
|
|
236
|
+
popoverElement = this.generatePopover();
|
|
285
237
|
}
|
|
286
238
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
*/
|
|
290
|
-
protected hidden(dispatcher: Element|null = null) {
|
|
291
|
-
this.toggleOptionalClasses(false);
|
|
292
|
-
super.hidden(dispatcher);
|
|
239
|
+
if (!popoverElement) {
|
|
240
|
+
throw "unable to find or generate popover element";
|
|
293
241
|
}
|
|
294
242
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
*/
|
|
298
|
-
protected bindDocumentEvents() {
|
|
299
|
-
this.boundHideOnOutsideClick = this.boundHideOnOutsideClick || this.hideOnOutsideClick.bind(this);
|
|
300
|
-
this.boundHideOnEscapePress = this.boundHideOnEscapePress || this.hideOnEscapePress.bind(this);
|
|
243
|
+
this.popoverElement = popoverElement;
|
|
244
|
+
}
|
|
301
245
|
|
|
302
|
-
|
|
303
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Determines the correct dispatching element from a potential input
|
|
248
|
+
* @param dispatcher The event or element to get the dispatcher from
|
|
249
|
+
*/
|
|
250
|
+
protected getDispatcher(dispatcher: Event|Element|null = null) : Element {
|
|
251
|
+
if (dispatcher instanceof Event) {
|
|
252
|
+
return <Element>dispatcher.target;
|
|
304
253
|
}
|
|
254
|
+
else if (dispatcher instanceof Element) {
|
|
255
|
+
return dispatcher;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
return this.element;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
305
261
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Schedules the popover to update on the next animation frame if visible
|
|
264
|
+
*/
|
|
265
|
+
protected scheduleUpdate() {
|
|
266
|
+
if (this.popper && this.isVisible) {
|
|
267
|
+
this.popper.update();
|
|
312
268
|
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
313
271
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
* @param {Event} e - The document click event
|
|
317
|
-
*/
|
|
318
|
-
private hideOnOutsideClick(e: MouseEvent) {
|
|
319
|
-
const target = <Node>e.target;
|
|
320
|
-
// check if the document was clicked inside either the reference element or the popover itself
|
|
321
|
-
// note: .contains also returns true if the node itself matches the target element
|
|
322
|
-
if (this.shouldHideOnOutsideClick && !this.referenceElement.contains(target) && !this.popoverElement!.contains(target) && document.body.contains(target)) {
|
|
323
|
-
this.hide(e);
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Forces the popover to hide if the user presses escape while it, one of its childen, or the reference element are focused
|
|
329
|
-
* @param {Event} e - The document keyup event
|
|
330
|
-
*/
|
|
331
|
-
private hideOnEscapePress(e: KeyboardEvent) {
|
|
332
|
-
// if the ESC key (27) wasn't pressed or if no popovers are showing, return
|
|
333
|
-
if (e.which !== 27 || !this.isVisible) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
272
|
+
export class PopoverController extends BasePopoverController {
|
|
273
|
+
static targets = [];
|
|
336
274
|
|
|
337
|
-
|
|
338
|
-
// note: .contains also returns true if the node itself matches the target element
|
|
339
|
-
if (this.popoverElement!.contains(<Node>e.target)) {
|
|
340
|
-
this.referenceElement.focus();
|
|
341
|
-
}
|
|
275
|
+
protected popoverSelectorAttribute = "aria-controls";
|
|
342
276
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Toggles all classes on the originating element based on the `class-toggle` data
|
|
348
|
-
* @param {boolean=} show - A boolean indicating whether this is being triggered by a show or hide.
|
|
349
|
-
*/
|
|
350
|
-
private toggleOptionalClasses(show?: boolean) {
|
|
351
|
-
if (!this.data.has("toggle-class")) {
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
var cl = this.referenceElement.classList;
|
|
355
|
-
this.data.get("toggle-class")!.split(/\s+/).forEach(function (cls: string) {
|
|
356
|
-
cl.toggle(cls, show);
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
}
|
|
277
|
+
private boundHideOnOutsideClick!: any;
|
|
278
|
+
private boundHideOnEscapePress!: any;
|
|
360
279
|
|
|
361
280
|
/**
|
|
362
|
-
*
|
|
363
|
-
* @param element the element the `data-controller="s-popover"` attribute is on
|
|
281
|
+
* Toggles optional classes in addition to BasePopoverController.shown
|
|
364
282
|
*/
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
controller.show();
|
|
369
|
-
} else if (isPopover) {
|
|
370
|
-
element.setAttribute("data-s-popover-auto-show", "true");
|
|
371
|
-
} else {
|
|
372
|
-
throw `element does not have data-controller="s-popover"`;
|
|
373
|
-
}
|
|
283
|
+
protected shown(dispatcher: Element|null = null) {
|
|
284
|
+
this.toggleOptionalClasses(true);
|
|
285
|
+
super.shown(dispatcher);
|
|
374
286
|
}
|
|
375
287
|
|
|
376
288
|
/**
|
|
377
|
-
*
|
|
378
|
-
* @param element the element the `data-controller="s-popover"` attribute is on
|
|
289
|
+
* Toggles optional classes in addition to BasePopoverController.hidden
|
|
379
290
|
*/
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (controller) {
|
|
384
|
-
controller.hide();
|
|
385
|
-
} else if (isPopover) {
|
|
386
|
-
element.removeAttribute("data-s-popover-auto-show");
|
|
387
|
-
if (popover) {
|
|
388
|
-
popover.classList.remove("is-visible");
|
|
389
|
-
}
|
|
390
|
-
} else {
|
|
391
|
-
throw `element does not have data-controller="s-popover"`;
|
|
392
|
-
}
|
|
291
|
+
protected hidden(dispatcher: Element|null = null) {
|
|
292
|
+
this.toggleOptionalClasses(false);
|
|
293
|
+
super.hidden(dispatcher);
|
|
393
294
|
}
|
|
394
295
|
|
|
395
296
|
/**
|
|
396
|
-
*
|
|
397
|
-
* @see Stacks.attachPopover
|
|
297
|
+
* Binds global events to the document for hiding popovers on user interaction
|
|
398
298
|
*/
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
* When set, `data-s-popover-placement` will be set to this value on the controller element.
|
|
406
|
-
*/
|
|
407
|
-
placement?: string;
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* When true, the popover will appear immediately when the controller connects.
|
|
411
|
-
*/
|
|
412
|
-
autoShow?: boolean;
|
|
299
|
+
protected bindDocumentEvents() {
|
|
300
|
+
this.boundHideOnOutsideClick = this.boundHideOnOutsideClick || this.hideOnOutsideClick.bind(this);
|
|
301
|
+
this.boundHideOnEscapePress = this.boundHideOnEscapePress || this.hideOnEscapePress.bind(this);
|
|
302
|
+
|
|
303
|
+
document.addEventListener("mousedown", this.boundHideOnOutsideClick);
|
|
304
|
+
document.addEventListener("keyup", this.boundHideOnEscapePress);
|
|
413
305
|
}
|
|
414
306
|
|
|
415
307
|
/**
|
|
416
|
-
*
|
|
417
|
-
* @param element the element that will receive the `data-controller="s-popover"` attribute.
|
|
418
|
-
* @param popover an element with the `.s-popover` class or HTML string containing a single element with the `.s-popover` class.
|
|
419
|
-
* If the popover does not have a parent element, it will be inserted as a immediately after the reference element.
|
|
420
|
-
* @param options an optional collection of options to use when configuring the popover.
|
|
308
|
+
* Unbinds global events to the document for hiding popovers on user interaction
|
|
421
309
|
*/
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
310
|
+
protected unbindDocumentEvents() {
|
|
311
|
+
document.removeEventListener("mousedown", this.boundHideOnOutsideClick);
|
|
312
|
+
document.removeEventListener("keyup", this.boundHideOnEscapePress);
|
|
313
|
+
}
|
|
425
314
|
|
|
426
|
-
|
|
427
|
-
|
|
315
|
+
/**
|
|
316
|
+
* Forces the popover to hide if a user clicks outside of it or its reference element
|
|
317
|
+
* @param {Event} e - The document click event
|
|
318
|
+
*/
|
|
319
|
+
private hideOnOutsideClick(e: MouseEvent) {
|
|
320
|
+
const target = <Node>e.target;
|
|
321
|
+
// check if the document was clicked inside either the reference element or the popover itself
|
|
322
|
+
// note: .contains also returns true if the node itself matches the target element
|
|
323
|
+
if (this.shouldHideOnOutsideClick && !this.referenceElement.contains(target) && !this.popoverElement!.contains(target) && document.body.contains(target)) {
|
|
324
|
+
this.hide(e);
|
|
428
325
|
}
|
|
326
|
+
};
|
|
429
327
|
|
|
430
|
-
|
|
431
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Forces the popover to hide if the user presses escape while it, one of its childen, or the reference element are focused
|
|
330
|
+
* @param {Event} e - The document keyup event
|
|
331
|
+
*/
|
|
332
|
+
private hideOnEscapePress(e: KeyboardEvent) {
|
|
333
|
+
// if the ESC key (27) wasn't pressed or if no popovers are showing, return
|
|
334
|
+
if (e.which !== 27 || !this.isVisible) {
|
|
335
|
+
return;
|
|
432
336
|
}
|
|
433
337
|
|
|
434
|
-
if
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
popover = elements[0];
|
|
338
|
+
// check if the target was inside the popover element and refocus the triggering element
|
|
339
|
+
// note: .contains also returns true if the node itself matches the target element
|
|
340
|
+
if (this.popoverElement!.contains(<Node>e.target)) {
|
|
341
|
+
this.referenceElement.focus();
|
|
440
342
|
}
|
|
441
343
|
|
|
442
|
-
|
|
443
|
-
|
|
344
|
+
this.hide(e);
|
|
345
|
+
};
|
|
444
346
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Toggles all classes on the originating element based on the `class-toggle` data
|
|
349
|
+
* @param {boolean=} show - A boolean indicating whether this is being triggered by a show or hide.
|
|
350
|
+
*/
|
|
351
|
+
private toggleOptionalClasses(show?: boolean) {
|
|
352
|
+
if (!this.data.has("toggle-class")) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
var cl = this.referenceElement.classList;
|
|
356
|
+
this.data.get("toggle-class")!.split(/\s+/).forEach(function (cls: string) {
|
|
357
|
+
cl.toggle(cls, show);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
448
361
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Helper to manually show an s-popover element via external JS
|
|
364
|
+
* @param element the element the `data-controller="s-popover"` attribute is on
|
|
365
|
+
*/
|
|
366
|
+
export function showPopover(element: HTMLElement) {
|
|
367
|
+
const { isPopover, controller } = getPopover(element);
|
|
368
|
+
if (controller) {
|
|
369
|
+
controller.show();
|
|
370
|
+
} else if (isPopover) {
|
|
371
|
+
element.setAttribute("data-s-popover-auto-show", "true");
|
|
372
|
+
} else {
|
|
373
|
+
throw `element does not have data-controller="s-popover"`;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
452
376
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Helper to manually hide an s-popover element via external JS
|
|
379
|
+
* @param element the element the `data-controller="s-popover"` attribute is on
|
|
380
|
+
*/
|
|
381
|
+
export function hidePopover(element: Element) {
|
|
382
|
+
const { isPopover, controller, popover } = getPopover(element);
|
|
383
|
+
|
|
384
|
+
if (controller) {
|
|
385
|
+
controller.hide();
|
|
386
|
+
} else if (isPopover) {
|
|
387
|
+
element.removeAttribute("data-s-popover-auto-show");
|
|
388
|
+
if (popover) {
|
|
389
|
+
popover.classList.remove("is-visible");
|
|
390
|
+
}
|
|
391
|
+
} else {
|
|
392
|
+
throw `element does not have data-controller="s-popover"`;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
457
395
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Options to use when attaching a popover via `Stacks.attachPopover`.
|
|
398
|
+
* @see Stacks.attachPopover
|
|
399
|
+
*/
|
|
400
|
+
export interface PopoverOptions {
|
|
401
|
+
/**
|
|
402
|
+
* When true, the `click->s-popover#toggle` action will be attached to the controller element or reference element.
|
|
403
|
+
*/
|
|
404
|
+
toggleOnClick?: boolean;
|
|
405
|
+
/**
|
|
406
|
+
* When set, `data-s-popover-placement` will be set to this value on the controller element.
|
|
407
|
+
*/
|
|
408
|
+
placement?: string;
|
|
461
409
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
410
|
+
/**
|
|
411
|
+
* When true, the popover will appear immediately when the controller connects.
|
|
412
|
+
*/
|
|
413
|
+
autoShow?: boolean;
|
|
414
|
+
}
|
|
465
415
|
|
|
466
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Attaches a popover to an element and performs additional configuration.
|
|
418
|
+
* @param element the element that will receive the `data-controller="s-popover"` attribute.
|
|
419
|
+
* @param popover an element with the `.s-popover` class or HTML string containing a single element with the `.s-popover` class.
|
|
420
|
+
* If the popover does not have a parent element, it will be inserted as a immediately after the reference element.
|
|
421
|
+
* @param options an optional collection of options to use when configuring the popover.
|
|
422
|
+
*/
|
|
423
|
+
export function attachPopover(element: Element, popover: Element | string, options?: PopoverOptions)
|
|
424
|
+
{
|
|
425
|
+
const { referenceElement, popover: existingPopover } = getPopover(element);
|
|
426
|
+
|
|
427
|
+
if (existingPopover) {
|
|
428
|
+
throw `element already has popover with id="${existingPopover.id}"`
|
|
429
|
+
}
|
|
467
430
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
element.setAttribute("data-s-popover-auto-show", "true");
|
|
477
|
-
}
|
|
431
|
+
if (!referenceElement) {
|
|
432
|
+
throw `element has invalid data-s-popover-reference-selector attribute`
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (typeof popover === 'string') {
|
|
436
|
+
const elements = document.createRange().createContextualFragment(popover).children;
|
|
437
|
+
if (elements.length !== 1) {
|
|
438
|
+
throw "popover should contain a single element";
|
|
478
439
|
}
|
|
440
|
+
popover = elements[0];
|
|
479
441
|
}
|
|
480
442
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
* @param element the element that has the `data-controller="s-popover"` attribute.
|
|
484
|
-
* @returns The popover that was attached to the element.
|
|
485
|
-
*/
|
|
486
|
-
export function detachPopover(element: Element) {
|
|
487
|
-
const { isPopover, controller, referenceElement, popover } = getPopover(element);
|
|
443
|
+
const existingId = referenceElement.getAttribute("aria-controls");
|
|
444
|
+
var popoverId = popover.id;
|
|
488
445
|
|
|
489
|
-
|
|
490
|
-
|
|
446
|
+
if (!popover.classList.contains('s-popover')) {
|
|
447
|
+
throw `popover should have the "s-popover" class but had class="${popover.className}"`;
|
|
448
|
+
}
|
|
491
449
|
|
|
492
|
-
|
|
493
|
-
popover
|
|
450
|
+
if (existingId && existingId !== popoverId) {
|
|
451
|
+
throw `element has aria-controls="${existingId}" but popover has id="${popoverId}"`;
|
|
452
|
+
}
|
|
494
453
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
referenceElement.removeAttribute("aria-controls");
|
|
500
|
-
}
|
|
501
|
-
}
|
|
454
|
+
if (!popoverId) {
|
|
455
|
+
popoverId = "--stacks-s-popover-" + Math.random().toString(36).substring(2, 10);
|
|
456
|
+
popover.id = popoverId;
|
|
457
|
+
}
|
|
502
458
|
|
|
503
|
-
|
|
459
|
+
if (!existingId) {
|
|
460
|
+
referenceElement.setAttribute("aria-controls", popoverId);
|
|
504
461
|
}
|
|
505
462
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
isPopover: boolean,
|
|
509
|
-
/** element's existing `PopoverController` or null it it has not been configured yet */
|
|
510
|
-
controller: PopoverController | null,
|
|
511
|
-
/** popover's reference element as would live in `referenceSelector` or null if invalid */
|
|
512
|
-
referenceElement: Element | null,
|
|
513
|
-
/** popover currently associated with the controller, or null if one does not exist in the DOM */
|
|
514
|
-
popover: HTMLElement | null
|
|
463
|
+
if (!popover.parentElement && element.parentElement) {
|
|
464
|
+
referenceElement.insertAdjacentElement("afterend", popover);
|
|
515
465
|
}
|
|
516
466
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return { isPopover, controller, referenceElement, popover };
|
|
467
|
+
toggleController(element, "s-popover", true);
|
|
468
|
+
|
|
469
|
+
if (options) {
|
|
470
|
+
if (options.toggleOnClick) {
|
|
471
|
+
referenceElement.setAttribute("data-action", "click->s-popover#toggle");
|
|
472
|
+
}
|
|
473
|
+
if (options.placement) {
|
|
474
|
+
element.setAttribute("data-s-popover-placement", options.placement);
|
|
475
|
+
}
|
|
476
|
+
if (options.autoShow) {
|
|
477
|
+
element.setAttribute("data-s-popover-auto-show", "true");
|
|
478
|
+
}
|
|
530
479
|
}
|
|
480
|
+
}
|
|
531
481
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
482
|
+
/**
|
|
483
|
+
* Removes the popover controller from an element and removes the popover from the DOM.
|
|
484
|
+
* @param element the element that has the `data-controller="s-popover"` attribute.
|
|
485
|
+
* @returns The popover that was attached to the element.
|
|
486
|
+
*/
|
|
487
|
+
export function detachPopover(element: Element) {
|
|
488
|
+
const { isPopover, controller, referenceElement, popover } = getPopover(element);
|
|
489
|
+
|
|
490
|
+
// Hide the popover so its events fire.
|
|
491
|
+
controller?.hide();
|
|
492
|
+
|
|
493
|
+
// Remove the popover if it exists
|
|
494
|
+
popover?.remove();
|
|
495
|
+
|
|
496
|
+
// Remove the popover controller and the aria-controls attributes.
|
|
497
|
+
if (isPopover) {
|
|
498
|
+
toggleController(element, "s-popover", false);
|
|
499
|
+
if (referenceElement) {
|
|
500
|
+
referenceElement.removeAttribute("aria-controls");
|
|
544
501
|
}
|
|
545
|
-
el.setAttribute('data-controller', Array.from(controllers).join(' '))
|
|
546
502
|
}
|
|
503
|
+
|
|
504
|
+
return popover;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
interface GetPopoverResult {
|
|
508
|
+
/** indicates whether or not the element has s-popover in its `data-controller` class */
|
|
509
|
+
isPopover: boolean,
|
|
510
|
+
/** element's existing `PopoverController` or null it it has not been configured yet */
|
|
511
|
+
controller: PopoverController | null,
|
|
512
|
+
/** popover's reference element as would live in `referenceSelector` or null if invalid */
|
|
513
|
+
referenceElement: Element | null,
|
|
514
|
+
/** popover currently associated with the controller, or null if one does not exist in the DOM */
|
|
515
|
+
popover: HTMLElement | null
|
|
547
516
|
}
|
|
548
517
|
|
|
549
|
-
|
|
518
|
+
/**
|
|
519
|
+
* Gets the current state of an element that may be or is intended to be an s-popover controller
|
|
520
|
+
* so it can be configured either directly or via the DOM.
|
|
521
|
+
* @param element An element that may have `data-controller="s-popover"`.
|
|
522
|
+
*/
|
|
523
|
+
function getPopover(element: Element): GetPopoverResult {
|
|
524
|
+
const isPopover = element.getAttribute("data-controller")?.includes("s-popover") || false;
|
|
525
|
+
const controller = Stacks.application.getControllerForElementAndIdentifier(element, "s-popover") as PopoverController;
|
|
526
|
+
const referenceSelector = element.getAttribute("data-s-popover-reference-selector");
|
|
527
|
+
const referenceElement = referenceSelector ? element.querySelector(referenceSelector) : element;
|
|
528
|
+
const popoverId = referenceElement ? referenceElement.getAttribute("aria-controls") : null;
|
|
529
|
+
const popover = popoverId ? document.getElementById(popoverId) : null;
|
|
530
|
+
return { isPopover, controller, referenceElement, popover };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Adds or removes the controller from an element's [data-controller] attribute without altering existing entries
|
|
535
|
+
* @param el The element to alter
|
|
536
|
+
* @param controllerName The name of the controller to add/remove
|
|
537
|
+
* @param include Whether to add the controllerName value
|
|
538
|
+
*/
|
|
539
|
+
function toggleController(el: Element, controllerName: string, include: boolean) {
|
|
540
|
+
var controllers = new Set(el.getAttribute('data-controller')?.split(/\s+/));
|
|
541
|
+
if (include) {
|
|
542
|
+
controllers.add(controllerName);
|
|
543
|
+
} else {
|
|
544
|
+
controllers.delete(controllerName);
|
|
545
|
+
}
|
|
546
|
+
el.setAttribute('data-controller', Array.from(controllers).join(' '))
|
|
547
|
+
}
|