toggle-event-source-polyfill 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeroen Zwartepoorte
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # ToggleEvent.source Polyfill
2
+
3
+ [![Node CI](https://github.com/jpzwarte/toggle-event-source-polyfill/actions/workflows/test.yml/badge.svg)](https://github.com/jpzwarte/toggle-event-source-polyfill/actions/workflows/test.yml)
4
+
5
+ This polyfills [`ToggleEvent.source`](https://developer.mozilla.org/en-US/docs/Web/API/ToggleEvent/source):
6
+ the element which caused a popover or `<dialog>` to be shown or hidden.
7
+
8
+ ```html
9
+ <button commandfor="my-popover" command="toggle-popover">Toggle</button>
10
+ <div popover id="my-popover">I'm a popover!</div>
11
+ <script>
12
+ document.getElementById("my-popover").addEventListener("toggle", (event) => {
13
+ // The button which opened or closed the popover, or null if it was
14
+ // toggled programmatically.
15
+ console.log(event.source);
16
+ });
17
+ </script>
18
+ ```
19
+
20
+ `source` landed well after popovers, dialogs and invoker commands themselves
21
+ (Chrome 140, Firefox 145, Safari 26.5), so it is missing in plenty of browsers
22
+ which otherwise support everything you would use it with.
23
+
24
+ ## Installation
25
+
26
+ ### With npm
27
+
28
+ If you're using npm, you only need to import the package, like so:
29
+
30
+ ```js
31
+ import "toggle-event-source-polyfill";
32
+ ```
33
+
34
+ This will automatically apply the polyfill if required.
35
+
36
+ If you'd like to manually apply the polyfill, you can instead import the
37
+ `isSupported` and `apply` functions directly from the `./toggle-event-source.js`
38
+ file, which is mapped to `/fn`:
39
+
40
+ ```js
41
+ import { isSupported, apply } from "toggle-event-source-polyfill/fn";
42
+ if (!isSupported()) apply();
43
+ ```
44
+
45
+ An `isPolyfilled` function is also available, to detect if it has been polyfilled:
46
+
47
+ ```js
48
+ import { isSupported, isPolyfilled, apply } from "toggle-event-source-polyfill/fn";
49
+ if (!isSupported() && !isPolyfilled()) apply();
50
+ ```
51
+
52
+ Alternatively, if you're not using a package manager, you can use the `unpkg` script:
53
+
54
+ ```html
55
+ <!-- polyfill automatically -->
56
+ <script
57
+ type="module"
58
+ async
59
+ src="https://unpkg.com/toggle-event-source-polyfill@latest/toggle-event-source.min.js"
60
+ ></script>
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ A source is reported for popovers and dialogs toggled by:
66
+
67
+ - a [`command`/`commandfor`](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API) button
68
+ - a [`popovertarget`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#popovertarget) button
69
+ - `showPopover({ source })` and `togglePopover({ source })`
70
+
71
+ As per spec, `hidePopover()` and the `<dialog>` `show()`, `showModal()`,
72
+ `close()` and `requestClose()` methods have no source, and neither does a
73
+ popover closed by light dismiss or by a close request - in all of those cases
74
+ `event.source` is `null`.
75
+
76
+ The polyfill also makes `new ToggleEvent(type, { source })` accept a source.
77
+
78
+ ### Together with a `command`/`commandfor` polyfill
79
+
80
+ The polyfill reads `commandForElement`, `command`, `popoverTargetElement` and
81
+ `popoverTargetAction` off the button, so it works both with native invoker
82
+ commands and with a polyfill such as
83
+ [invokers-polyfill](https://github.com/keithamus/invokers-polyfill) providing
84
+ them. No cooperation between the two is needed, but **apply this polyfill
85
+ first**:
86
+
87
+ ```js
88
+ import "toggle-event-source-polyfill";
89
+ import "invokers-polyfill";
90
+ ```
91
+
92
+ A `command`/`commandfor` polyfill hides popovers and opens dialogs by calling
93
+ `hidePopover()`, `showModal()`, `close()` and `requestClose()`, none of which
94
+ carry a source of their own. This polyfill recognises those calls as belonging
95
+ to the click it is handling, which requires its click listener to run first. In
96
+ the other order the `toggle` event still reports the right source, but
97
+ `beforetoggle` - which those methods fire synchronously, before this polyfill
98
+ has seen the click - reports `null`.
99
+
100
+ ## Limitations
101
+
102
+ - The polyfill hands the source to `toggle`/`beforetoggle` listeners from a
103
+ capture phase listener on the popover's root node, so a capture phase
104
+ listener registered on that root _before_ the polyfill is applied will see
105
+ `event.source` as `null`. Apply the polyfill as early as possible.
106
+ - Setting a source does not make that element the popover's
107
+ [implicit anchor element](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning),
108
+ which cannot be polyfilled.
109
+ - Buttons inside a closed shadow root that was created before the polyfill was
110
+ applied are not detected as sources.
111
+ - If a popover polyfill is used, it must be loaded _before_ this polyfill, as it
112
+ replaces the popover methods this polyfill wraps.
113
+ - If an invoker's activation is cancelled (by calling `preventDefault()` on its
114
+ `command` event, for example) and the same popover or dialog is later changed
115
+ in the same direction without a source, that earlier invoker may be reported
116
+ as the source.
117
+
118
+ ## Demo
119
+
120
+ `npm start` opens the demo in Playwright's WebKit 26.0 - a real browser without
121
+ `ToggleEvent.source` - so you can try the polyfill in a browser which actually
122
+ needs it. Closing the browser window stops the dev server. `npm run dev` serves
123
+ the same page in your own browser at http://localhost:5173/ instead.
124
+
125
+ The page logs the source of every toggle event, and the button in its header
126
+ reports whether the polyfill is loaded:
127
+
128
+ - **Apply polyfill** - this browser has no `ToggleEvent.source`. Clicking
129
+ reloads the page with `?polyfill`, which applies it.
130
+ - **Polyfill applied** - loaded with `?polyfill`. Clicking drops the parameter
131
+ again, so you can see the same buttons with and without it.
132
+ - **No polyfill needed** - disabled, because the browser implements
133
+ `ToggleEvent.source` itself.
134
+
135
+ The button reloads rather than applying the polyfill in place, because the
136
+ polyfill hands sources to listeners from a capture phase listener on the
137
+ popover's root node: anything registered before it applies would see a null
138
+ source. Reloading with a query parameter keeps it first on the page.
139
+
140
+ ## Testing
141
+
142
+ The suite runs in [Playwright](https://playwright.dev) and drives `index.html` -
143
+ the demo's own buttons are clicked, and the source each `toggle` and
144
+ `beforetoggle` reports is checked against the spec. Cases with no button of
145
+ their own - the `ToggleEvent` constructor, the popover and dialog methods,
146
+ shadow DOM retargeting - run as scripts inside the same page.
147
+
148
+ ```sh
149
+ npm install
150
+ npx playwright install
151
+ npm test
152
+ ```
153
+
154
+ There are two projects:
155
+
156
+ - **WebKit 26.0**, the newest build Playwright ships which has popovers, dialogs
157
+ and `command`/`commandfor` but _not_ `ToggleEvent.source`. The polyfill is the
158
+ only missing piece there, so this is where it is exercised for real.
159
+ - **Chromium**, which implements `ToggleEvent.source` itself. As well as running
160
+ the polyfill suites, it runs `native.spec.js`: the same expectations against
161
+ the browser's own implementation. That is what keeps the suite honest, rather
162
+ than only proving the polyfill agrees with itself.
163
+
164
+ `@playwright/test` is pinned to an exact version on purpose: Playwright 1.59 and
165
+ later bundle WebKit 26.4, which has `ToggleEvent.source` natively and would
166
+ leave the polyfill untested. `support.spec.js` asserts what each project is
167
+ expected to support, so an accidental upgrade fails loudly instead of quietly
168
+ testing nothing.
169
+
170
+ `npm run test:ui` opens Playwright's UI mode.
171
+
172
+ ## Releasing
173
+
174
+ `0.0.1` has to be published by hand, because npm can only be told to trust a
175
+ publisher for a package which already exists:
176
+
177
+ ```sh
178
+ npm login
179
+ npm version 0.0.1 --git-tag-version=false
180
+ npm publish --access public # prompts for your 2FA one-time password
181
+ git checkout package.json package-lock.json
182
+ ```
183
+
184
+ `prepublishOnly` builds `toggle-event-source.min.js`, and the version is
185
+ reverted afterwards because the repository keeps a placeholder version in git -
186
+ releases take their version from the tag instead.
187
+
188
+ Then set up trusted publishing, so CI never needs a token. On npmjs.com, under
189
+ the package's **Settings → Trusted Publisher**, choose GitHub Actions and enter:
190
+
191
+ | Field | Value |
192
+ | -------------------- | ------------------------------ |
193
+ | Organization or user | `jpzwarte` |
194
+ | Repository | `toggle-event-source-polyfill` |
195
+ | Workflow filename | `publish.yml` |
196
+ | Environment | leave empty |
197
+
198
+ Every release after that is automatic: create a GitHub release tagged `v0.0.2`,
199
+ `v0.1.0` and so on, and `.github/workflows/publish.yml` runs the tests, builds
200
+ the minified bundle, sets the version from the tag, and publishes with
201
+ provenance.
202
+
203
+ ## Acknowledgements
204
+
205
+ Extracted from, and designed to work alongside,
206
+ [invokers-polyfill](https://github.com/keithamus/invokers-polyfill) by Keith
207
+ Cirkel, which polyfills the `command`/`commandfor` attributes themselves and is
208
+ MIT licensed.
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /// <reference path="./toggle-event-source.d.ts" />
2
+
3
+ export {};
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { apply, isSupported } from "./toggle-event-source.js";
2
+ if (!isSupported()) apply();
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "toggle-event-source-polyfill",
3
+ "version": "0.0.1",
4
+ "description": "This polyfills `ToggleEvent.source`, the element which caused a popover or `<dialog>` to be shown or hidden.",
5
+ "keywords": [
6
+ "command",
7
+ "commandfor",
8
+ "dialog",
9
+ "invokers",
10
+ "polyfill",
11
+ "popover",
12
+ "toggleevent"
13
+ ],
14
+ "homepage": "https://github.com/jpzwarte/toggle-event-source-polyfill#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/jpzwarte/toggle-event-source-polyfill/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Jeroen Zwartepoorte",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/jpzwarte/toggle-event-source-polyfill.git"
23
+ },
24
+ "files": [
25
+ "index.js",
26
+ "index.d.ts",
27
+ "toggle-event-source.js",
28
+ "toggle-event-source.d.ts",
29
+ "toggle-event-source.min.js"
30
+ ],
31
+ "type": "module",
32
+ "main": "index.js",
33
+ "types": "index.d.ts",
34
+ "typesVersions": {
35
+ "*": {
36
+ "fn": [
37
+ "./toggle-event-source.d.ts"
38
+ ]
39
+ }
40
+ },
41
+ "exports": {
42
+ ".": {
43
+ "types": "./index.d.ts",
44
+ "import": "./index.js",
45
+ "browser": "./toggle-event-source.min.js",
46
+ "require": "./toggle-event-source.min.js"
47
+ },
48
+ "./fn": {
49
+ "types": "./toggle-event-source.d.ts",
50
+ "import": "./toggle-event-source.js"
51
+ }
52
+ },
53
+ "scripts": {
54
+ "start": "node scripts/start.js",
55
+ "dev": "vite",
56
+ "minify": "esbuild --bundle --minify index.js > toggle-event-source.min.js",
57
+ "prepublishOnly": "npm run minify",
58
+ "format": "oxfmt .",
59
+ "test": "playwright test",
60
+ "test:ui": "playwright test --ui"
61
+ },
62
+ "devDependencies": {
63
+ "@playwright/test": "1.58.2",
64
+ "esbuild": "^0.28.2",
65
+ "oxfmt": "^0.64.0",
66
+ "vite": "^8.2.2"
67
+ }
68
+ }
@@ -0,0 +1,12 @@
1
+ export declare function isSupported(): boolean;
2
+ export declare function isPolyfilled(): boolean;
3
+ export declare function apply(): void;
4
+
5
+ declare global {
6
+ interface ToggleEvent {
7
+ readonly source: Element | null;
8
+ }
9
+ interface ToggleEventInit {
10
+ source?: Element | null;
11
+ }
12
+ }
@@ -0,0 +1,357 @@
1
+ // Polyfill for `ToggleEvent.source`: the element which caused a popover or
2
+ // `<dialog>` to be shown or hidden.
3
+ //
4
+ // The `toggle`/`beforetoggle` events themselves are dispatched by the browser,
5
+ // so this polyfill records the element responsible for a state change right
6
+ // before the change happens, then hands it to the event during the capture
7
+ // phase - which runs before any listener on the popover/dialog itself.
8
+
9
+ const ELEMENT_NODE = 1;
10
+
11
+ const NativeToggleEvent = globalThis.ToggleEvent;
12
+
13
+ // event -> source element (or null)
14
+ const eventSources = new WeakMap();
15
+ // popover/dialog -> { source, newState, activation } for the state change which
16
+ // is about to happen
17
+ const pendingSources = new WeakMap();
18
+ const observedRoots = new WeakSet();
19
+
20
+ let applied = false;
21
+ // The click event currently being dispatched, if it activated an invoker.
22
+ let clickActivation = null;
23
+
24
+ function isElement(node) {
25
+ return Boolean(node) && node.nodeType === ELEMENT_NODE;
26
+ }
27
+
28
+ function getRootNode(node) {
29
+ if (node && typeof node.getRootNode === "function") {
30
+ return node.getRootNode();
31
+ }
32
+ if (node && node.parentNode) return getRootNode(node.parentNode);
33
+ return node;
34
+ }
35
+
36
+ // Node returning IDL attributes are retargeted against the tree the event is
37
+ // being observed in, so a source inside a shadow tree is reported as its host.
38
+ function retarget(source, event) {
39
+ if (!isElement(source)) return null;
40
+ const sourceRoot = getRootNode(source);
41
+ if (sourceRoot !== getRootNode(event.target || document)) {
42
+ return sourceRoot.host || null;
43
+ }
44
+ return source;
45
+ }
46
+
47
+ function isPopoverOpen(element) {
48
+ try {
49
+ return element.matches(":popover-open");
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ function isDialogOpen(element) {
56
+ return element.localName === "dialog" && element.hasAttribute("open");
57
+ }
58
+
59
+ class ToggleEvent extends (NativeToggleEvent || Event) {
60
+ constructor(type, toggleEventInit = {}) {
61
+ super(type, toggleEventInit);
62
+ const { source } = toggleEventInit;
63
+ if (source != null && !isElement(source)) {
64
+ throw new TypeError(`source must be an element`);
65
+ }
66
+ eventSources.set(this, source || null);
67
+ if (!NativeToggleEvent) {
68
+ const { oldState = "", newState = "" } = toggleEventInit;
69
+ Object.defineProperties(this, {
70
+ oldState: { value: String(oldState), enumerable: true },
71
+ newState: { value: String(newState), enumerable: true },
72
+ });
73
+ }
74
+ }
75
+
76
+ get [Symbol.toStringTag]() {
77
+ return "ToggleEvent";
78
+ }
79
+ }
80
+
81
+ // Events dispatched by the browser are instances of the native class, so
82
+ // `instanceof` has to keep working for those too.
83
+ if (NativeToggleEvent) {
84
+ Object.defineProperty(ToggleEvent, Symbol.hasInstance, {
85
+ configurable: true,
86
+ value: (value) => value instanceof NativeToggleEvent,
87
+ });
88
+ }
89
+
90
+ function setEventSource(event, source) {
91
+ eventSources.set(event, source);
92
+ // Events which aren't instances of the `ToggleEvent` we patched (for example
93
+ // ones dispatched by a popover polyfill) don't inherit the `source` getter.
94
+ if (!("source" in event)) {
95
+ Object.defineProperty(event, "source", {
96
+ enumerable: true,
97
+ configurable: true,
98
+ get() {
99
+ return retarget(eventSources.get(event) || null, event);
100
+ },
101
+ });
102
+ }
103
+ }
104
+
105
+ function handleToggleEvent(event) {
106
+ const pending = pendingSources.get(event.target);
107
+ if (!pending) return;
108
+ // A state change we didn't predict isn't the one we recorded a source for.
109
+ if (event.newState !== pending.newState) return;
110
+ if (event.type === "toggle") pendingSources.delete(event.target);
111
+ if (!pending.source) return;
112
+ setEventSource(event, pending.source);
113
+ }
114
+
115
+ function observeRootOf(element) {
116
+ const root = getRootNode(element);
117
+ if (!root || observedRoots.has(root)) return;
118
+ if (typeof root.addEventListener !== "function") return;
119
+ observedRoots.add(root);
120
+ root.addEventListener("beforetoggle", handleToggleEvent, true);
121
+ root.addEventListener("toggle", handleToggleEvent, true);
122
+ }
123
+
124
+ function setPendingSource(element, source, newState) {
125
+ if (!isElement(element)) return () => {};
126
+ const existing = pendingSources.get(element);
127
+ // `hidePopover()` and the `<dialog>` methods carry no source of their own.
128
+ // When one of them is called while an invoker click is being dispatched -
129
+ // which is how a `command`/`commandfor` polyfill drives them - it is that
130
+ // click, not the sourceless call, which describes the state change.
131
+ if (
132
+ !isElement(source) &&
133
+ existing &&
134
+ existing.source &&
135
+ existing.newState === newState &&
136
+ clickActivation !== null &&
137
+ existing.activation === clickActivation
138
+ ) {
139
+ return () => {};
140
+ }
141
+ const pending = {
142
+ source: isElement(source) ? source : null,
143
+ newState,
144
+ activation: clickActivation,
145
+ };
146
+ pendingSources.set(element, pending);
147
+ observeRootOf(element);
148
+ return () => {
149
+ if (pendingSources.get(element) === pending) pendingSources.delete(element);
150
+ };
151
+ }
152
+
153
+ function patchMethod(prototype, name, getPending) {
154
+ const method = prototype && prototype[name];
155
+ if (typeof method !== "function") return;
156
+ Object.defineProperty(prototype, name, {
157
+ ...Object.getOwnPropertyDescriptor(prototype, name),
158
+ value: function (...args) {
159
+ const { source, newState } = getPending.call(this, args);
160
+ const undo = setPendingSource(this, source, newState);
161
+ try {
162
+ return method.apply(this, args);
163
+ } catch (error) {
164
+ undo();
165
+ throw error;
166
+ }
167
+ },
168
+ });
169
+ }
170
+
171
+ function patchPopoverMethods() {
172
+ const prototype = globalThis.HTMLElement && HTMLElement.prototype;
173
+ if (!prototype || typeof prototype.showPopover !== "function") return;
174
+
175
+ patchMethod(prototype, "showPopover", function ([options]) {
176
+ return { source: options && options.source, newState: "open" };
177
+ });
178
+
179
+ // `hidePopover()` is specified to always hide with a null source.
180
+ patchMethod(prototype, "hidePopover", function () {
181
+ return { source: null, newState: "closed" };
182
+ });
183
+
184
+ patchMethod(prototype, "togglePopover", function ([options]) {
185
+ const dictionary = typeof options === "object" && options ? options : null;
186
+ const force = typeof options === "boolean" ? options : dictionary?.force;
187
+ const willShow = force == null ? !isPopoverOpen(this) : Boolean(force);
188
+ return {
189
+ // Only the showing branch of `togglePopover()` carries a source.
190
+ source: willShow && dictionary ? dictionary.source : null,
191
+ newState: willShow ? "open" : "closed",
192
+ };
193
+ });
194
+ }
195
+
196
+ function patchDialogMethods() {
197
+ const prototype = globalThis.HTMLDialogElement && HTMLDialogElement.prototype;
198
+ if (!prototype) return;
199
+ // None of these take a source; they are patched so a state change they cause
200
+ // isn't attributed to an invoker which is no longer responsible for it.
201
+ for (const [name, newState] of [
202
+ ["show", "open"],
203
+ ["showModal", "open"],
204
+ ["close", "closed"],
205
+ ["requestClose", "closed"],
206
+ ]) {
207
+ patchMethod(prototype, name, () => ({ source: null, newState }));
208
+ }
209
+ }
210
+
211
+ function recordActivationSource(element, source, newState, possible) {
212
+ const pending = pendingSources.get(element);
213
+ if (pending) {
214
+ // Something already performed the state change during this click - most
215
+ // likely a `command`/`commandfor` or popover polyfill acting on the button
216
+ // before this listener ran. It is recorded, it just has no source yet.
217
+ if (!pending.source) {
218
+ pending.source = source;
219
+ return;
220
+ }
221
+ // Or it recorded this same source itself, via `showPopover({ source })`.
222
+ if (pending.source === source) return;
223
+ }
224
+ if (possible) setPendingSource(element, source, newState);
225
+ }
226
+
227
+ function recordCommandSource(target, command, source) {
228
+ switch (command) {
229
+ case "show-popover":
230
+ if (target.popover) {
231
+ recordActivationSource(target, source, "open", !isPopoverOpen(target));
232
+ }
233
+ break;
234
+ case "hide-popover":
235
+ if (target.popover) {
236
+ recordActivationSource(target, source, "closed", isPopoverOpen(target));
237
+ }
238
+ break;
239
+ case "toggle-popover":
240
+ if (target.popover) {
241
+ const open = isPopoverOpen(target);
242
+ recordActivationSource(target, source, open ? "closed" : "open", true);
243
+ }
244
+ break;
245
+ case "show-modal":
246
+ if (target.localName === "dialog") {
247
+ recordActivationSource(target, source, "open", !isDialogOpen(target));
248
+ }
249
+ break;
250
+ case "close":
251
+ case "request-close":
252
+ if (target.localName === "dialog") {
253
+ recordActivationSource(target, source, "closed", isDialogOpen(target));
254
+ }
255
+ break;
256
+ }
257
+ }
258
+
259
+ function recordInvokerSource(node) {
260
+ if (node.localName !== "button" && node.localName !== "input") return false;
261
+
262
+ const commandFor = node.commandForElement;
263
+ const command = typeof node.command === "string" ? node.command : "";
264
+ if (commandFor && command) {
265
+ recordCommandSource(commandFor, command.toLowerCase(), node);
266
+ return true;
267
+ }
268
+
269
+ const popover = node.popoverTargetElement;
270
+ if (popover) {
271
+ const action = String(node.popoverTargetAction || "toggle").toLowerCase();
272
+ const open = isPopoverOpen(popover);
273
+ const possible = (action !== "show" || !open) && (action !== "hide" || open);
274
+ recordActivationSource(popover, node, open ? "closed" : "open", possible);
275
+ return true;
276
+ }
277
+
278
+ return false;
279
+ }
280
+
281
+ // Both `command`/`commandfor` and `popovertarget` buttons act on their target
282
+ // after the click event has finished dispatching, so recording the source
283
+ // during the capture phase is early enough.
284
+ function handleClick(event) {
285
+ if (event.type !== "click" || event.defaultPrevented) return;
286
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [event.target];
287
+ clickActivation = event;
288
+ let activated = false;
289
+ for (const node of path) {
290
+ // Only the innermost button/input can be the activation target.
291
+ if (isElement(node) && recordInvokerSource(node)) {
292
+ activated = true;
293
+ break;
294
+ }
295
+ }
296
+ if (!activated) {
297
+ clickActivation = null;
298
+ return;
299
+ }
300
+ // A polyfill driving this click acts on the target synchronously, so the
301
+ // activation only needs to outlive the current task.
302
+ setTimeout(() => {
303
+ if (clickActivation === event) clickActivation = null;
304
+ }, 0);
305
+ }
306
+
307
+ function observeShadowRoots(ElementClass, callback) {
308
+ const attachShadow = ElementClass.prototype.attachShadow;
309
+ ElementClass.prototype.attachShadow = function (init) {
310
+ const shadow = attachShadow.call(this, init);
311
+ callback(shadow);
312
+ return shadow;
313
+ };
314
+ const attachInternals = ElementClass.prototype.attachInternals;
315
+ if (typeof attachInternals !== "function") return;
316
+ ElementClass.prototype.attachInternals = function () {
317
+ const internals = attachInternals.call(this);
318
+ if (internals.shadowRoot) callback(internals.shadowRoot);
319
+ return internals;
320
+ };
321
+ }
322
+
323
+ export function isSupported() {
324
+ return (
325
+ typeof globalThis.ToggleEvent !== "undefined" && "source" in globalThis.ToggleEvent.prototype
326
+ );
327
+ }
328
+
329
+ export function isPolyfilled() {
330
+ return Boolean(globalThis.ToggleEvent) && !/native code/i.test(String(globalThis.ToggleEvent));
331
+ }
332
+
333
+ export function apply() {
334
+ if (applied) return;
335
+ applied = true;
336
+
337
+ Object.defineProperty((NativeToggleEvent || ToggleEvent).prototype, "source", {
338
+ enumerable: true,
339
+ configurable: true,
340
+ get() {
341
+ return retarget(eventSources.get(this) || null, this);
342
+ },
343
+ });
344
+
345
+ patchPopoverMethods();
346
+ patchDialogMethods();
347
+
348
+ document.addEventListener("click", handleClick, true);
349
+ observeRootOf(document);
350
+
351
+ observeShadowRoots(HTMLElement, (shadow) => {
352
+ shadow.addEventListener("click", handleClick, true);
353
+ observeRootOf(shadow);
354
+ });
355
+
356
+ Object.assign(globalThis, { ToggleEvent });
357
+ }
@@ -0,0 +1 @@
1
+ (()=>{var f=globalThis.ToggleEvent,h=new WeakMap,i=new WeakMap,w=new WeakSet,m=!1,u=null;function l(e){return!!e&&e.nodeType===1}function g(e){return e&&typeof e.getRootNode=="function"?e.getRootNode():e&&e.parentNode?g(e.parentNode):e}function E(e,t){if(!l(e))return null;let o=g(e);return o!==g(t.target||document)?o.host||null:e}function a(e){try{return e.matches(":popover-open")}catch{return!1}}function S(e){return e.localName==="dialog"&&e.hasAttribute("open")}var p=class extends(f||Event){constructor(t,o={}){super(t,o);let{source:n}=o;if(n!=null&&!l(n))throw new TypeError("source must be an element");if(h.set(this,n||null),!f){let{oldState:r="",newState:c=""}=o;Object.defineProperties(this,{oldState:{value:String(r),enumerable:!0},newState:{value:String(c),enumerable:!0}})}}get[Symbol.toStringTag](){return"ToggleEvent"}};f&&Object.defineProperty(p,Symbol.hasInstance,{configurable:!0,value:e=>e instanceof f});function O(e,t){h.set(e,t),"source"in e||Object.defineProperty(e,"source",{enumerable:!0,configurable:!0,get(){return E(h.get(e)||null,e)}})}function v(e){let t=i.get(e.target);t&&e.newState===t.newState&&(e.type==="toggle"&&i.delete(e.target),t.source&&O(e,t.source))}function b(e){let t=g(e);!t||w.has(t)||typeof t.addEventListener=="function"&&(w.add(t),t.addEventListener("beforetoggle",v,!0),t.addEventListener("toggle",v,!0))}function P(e,t,o){if(!l(e))return()=>{};let n=i.get(e);if(!l(t)&&n&&n.source&&n.newState===o&&u!==null&&n.activation===u)return()=>{};let r={source:l(t)?t:null,newState:o,activation:u};return i.set(e,r),b(e),()=>{i.get(e)===r&&i.delete(e)}}function d(e,t,o){let n=e&&e[t];typeof n=="function"&&Object.defineProperty(e,t,{...Object.getOwnPropertyDescriptor(e,t),value:function(...r){let{source:c,newState:y}=o.call(this,r),L=P(this,c,y);try{return n.apply(this,r)}catch(M){throw L(),M}}})}function R(){let e=globalThis.HTMLElement&&HTMLElement.prototype;!e||typeof e.showPopover!="function"||(d(e,"showPopover",function([t]){return{source:t&&t.source,newState:"open"}}),d(e,"hidePopover",function(){return{source:null,newState:"closed"}}),d(e,"togglePopover",function([t]){let o=typeof t=="object"&&t?t:null,n=typeof t=="boolean"?t:o?.force,r=n==null?!a(this):!!n;return{source:r&&o?o.source:null,newState:r?"open":"closed"}}))}function j(){let e=globalThis.HTMLDialogElement&&HTMLDialogElement.prototype;if(e)for(let[t,o]of[["show","open"],["showModal","open"],["close","closed"],["requestClose","closed"]])d(e,t,()=>({source:null,newState:o}))}function s(e,t,o,n){let r=i.get(e);if(r){if(!r.source){r.source=t;return}if(r.source===t)return}n&&P(e,t,o)}function D(e,t,o){switch(t){case"show-popover":e.popover&&s(e,o,"open",!a(e));break;case"hide-popover":e.popover&&s(e,o,"closed",a(e));break;case"toggle-popover":if(e.popover){let n=a(e);s(e,o,n?"closed":"open",!0)}break;case"show-modal":e.localName==="dialog"&&s(e,o,"open",!S(e));break;case"close":case"request-close":e.localName==="dialog"&&s(e,o,"closed",S(e));break}}function x(e){if(e.localName!=="button"&&e.localName!=="input")return!1;let t=e.commandForElement,o=typeof e.command=="string"?e.command:"";if(t&&o)return D(t,o.toLowerCase(),e),!0;let n=e.popoverTargetElement;if(n){let r=String(e.popoverTargetAction||"toggle").toLowerCase(),c=a(n);return s(n,e,c?"closed":"open",(r!=="show"||!c)&&(r!=="hide"||c)),!0}return!1}function T(e){if(e.type!=="click"||e.defaultPrevented)return;let t=typeof e.composedPath=="function"?e.composedPath():[e.target];u=e;let o=!1;for(let n of t)if(l(n)&&x(n)){o=!0;break}if(!o){u=null;return}setTimeout(()=>{u===e&&(u=null)},0)}function H(e,t){let o=e.prototype.attachShadow;e.prototype.attachShadow=function(r){let c=o.call(this,r);return t(c),c};let n=e.prototype.attachInternals;typeof n=="function"&&(e.prototype.attachInternals=function(){let r=n.call(this);return r.shadowRoot&&t(r.shadowRoot),r})}function N(){return typeof globalThis.ToggleEvent<"u"&&"source"in globalThis.ToggleEvent.prototype}function k(){m||(m=!0,Object.defineProperty((f||p).prototype,"source",{enumerable:!0,configurable:!0,get(){return E(h.get(this)||null,this)}}),R(),j(),document.addEventListener("click",T,!0),b(document),H(HTMLElement,e=>{e.addEventListener("click",T,!0),b(e)}),Object.assign(globalThis,{ToggleEvent:p}))}N()||k();})();