@excom/dialog-anchor 0.1.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.
Files changed (39) hide show
  1. package/.rush/temp/chunked-rush-logs/dialog-anchor.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/dialog-anchor.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/dialog-anchor.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +3 -0
  14. package/config/rig.json +5 -0
  15. package/dialog-anchor.css +1 -0
  16. package/dialog-anchor.ts +73 -0
  17. package/index.css +1 -0
  18. package/index.ts +17 -0
  19. package/package.json +44 -0
  20. package/rush-logs/dialog-anchor.apply-exports.cache.log +1 -0
  21. package/rush-logs/dialog-anchor.apply-exports.log +1 -0
  22. package/rush-logs/dialog-anchor.build_docs.cache.log +1 -0
  23. package/rush-logs/dialog-anchor.build_docs.log +1 -0
  24. package/rush-logs/dialog-anchor.build_package-metas.cache.log +1 -0
  25. package/rush-logs/dialog-anchor.build_package-metas.log +1 -0
  26. package/src/dialog-anchor.css +19 -0
  27. package/support/custom-elements.json +110 -0
  28. package/support/demos/auto-close.html +11 -0
  29. package/support/demos/is-modal.html +21 -0
  30. package/support/demos/simple.html +7 -0
  31. package/support/dist-docs/dialog-anchor.md +163 -0
  32. package/support/docs/INTERNAL.md +1 -0
  33. package/support/docs/README.md +58 -0
  34. package/support/package-meta.json +199 -0
  35. package/support/tests/auto-close.view.test.ts +31 -0
  36. package/support/tests/dialog-anchor.test.ts +102 -0
  37. package/support/tests/is-modal.view.test.ts +34 -0
  38. package/support/tests/simple.view.test.ts +31 -0
  39. package/tsconfig.json +5 -0
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 37.54774099999986
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 53.83026099999984
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 98.41211699999985
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "../../packages/dialog-anchor": "../../packages/dialog-anchor:iu+Gwd294io6cbeYmVbBu6c2aWWWMg9YuILC/ZbWLyY=:"
3
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
3
+ "rigPackageName": "@excom/heft-rig",
4
+ "rigProfile": "default"
5
+ }
@@ -0,0 +1 @@
1
+ @import "./index.css";
@@ -0,0 +1,73 @@
1
+ import { selectOne } from "@excom/kit-utils";
2
+ import { ListenableElement } from "@excom/listenable-element";
3
+ import { Neutron } from "@excom/neutron";
4
+
5
+ /**
6
+ * Click target that opens or closes a `<dialog>` without any JavaScript.
7
+ * Wrap a button (or any clickable content) in `<dialog-anchor>` and point
8
+ * it at a `<dialog>` via `target-ref`, or let it find the nearest ancestor
9
+ * `<dialog>` automatically — the common case for a close button living
10
+ * inside the dialog it closes.
11
+ *
12
+ * By default it listens for `click`; set `listen-for` (from
13
+ * `ListenableElement`) to toggle on another event instead — e.g. auto-close
14
+ * a dialog when a nested `<super-form>` succeeds.
15
+ *
16
+ * @summary Open/close a `<dialog>` — by selector or nearest ancestor.
17
+ *
18
+ * @example
19
+ * <dialog-anchor target-ref="#confirm" is-modal>
20
+ * <button>Delete</button>
21
+ * </dialog-anchor>
22
+ * <dialog id="confirm">...</dialog>
23
+ */
24
+ export const DialogAnchor = Neutron.compose([
25
+ ListenableElement,
26
+ Neutron({
27
+ tag: "dialog-anchor",
28
+ props: {
29
+ /**
30
+ * @option
31
+ * CSS selector for the `<dialog>` to toggle. If omitted, the
32
+ * element toggles the nearest ancestor `<dialog>`.
33
+ * @values <CSS Selector>
34
+ */
35
+ targetRef: String,
36
+ /**
37
+ * @option
38
+ * Open the dialog as a modal (blocks the rest of the page) instead
39
+ * of a non-modal popover.
40
+ */
41
+ isModal: Boolean,
42
+ },
43
+ }),
44
+ ])
45
+ .defineMethods({
46
+ actionHandler: (element) => {
47
+ let dialog =
48
+ element.targetRef &&
49
+ selectOne(element.targetRef, {
50
+ scope: element,
51
+ });
52
+ if (!(dialog instanceof HTMLDialogElement)) {
53
+ dialog = element.closest("dialog");
54
+ }
55
+ if (dialog instanceof HTMLDialogElement) {
56
+ const isOpen = dialog.open;
57
+ const isModal = element.isModal;
58
+ if (isOpen) {
59
+ dialog.close();
60
+ } else {
61
+ dialog[isModal ? "showModal" : "show"]();
62
+ }
63
+ }
64
+ },
65
+ })
66
+ .onConnected(
67
+ ({ listenFor, listenForLifecycle, handleEvent, isMoving }) =>
68
+ !isMoving &&
69
+ !listenFor?.length &&
70
+ !listenForLifecycle?.length && {
71
+ addListener: ["click", handleEvent],
72
+ }
73
+ );
package/index.css ADDED
@@ -0,0 +1 @@
1
+ @import "./src/dialog-anchor.css";
package/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { DialogAnchor } from "./dialog-anchor";
2
+
3
+ DialogAnchor.define();
4
+
5
+ export { DialogAnchor };
6
+
7
+ type T_HTMLDialogAnchorElement = typeof DialogAnchor.CustomElement;
8
+ declare global {
9
+ interface HTMLDialogAnchorElement extends T_HTMLDialogAnchorElement {}
10
+ interface Window {
11
+ HTMLDialogAnchorElement: HTMLDialogAnchorElement;
12
+ }
13
+ interface HTMLElementTagNameMap {
14
+ "dialog-anchor": HTMLDialogAnchorElement;
15
+ }
16
+ }
17
+ export type { HTMLDialogAnchorElement };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@excom/dialog-anchor",
3
+ "version": "0.1.0",
4
+ "description": "<dialog-anchor> custom element",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=24.13.0"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@excom/listenable-element": "^0.1.0",
12
+ "@excom/neutron": "^0.1.0",
13
+ "@excom/kit-utils": "^0.1.0"
14
+ },
15
+ "peerDependencies": {},
16
+ "devDependencies": {
17
+ "@excom/heft-rig": "^0.1.0"
18
+ },
19
+ "repository": {
20
+ "url": "excom-dev/nucleus",
21
+ "directory": "packages/dialog-anchor"
22
+ },
23
+ "homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/dialog-anchor/support/docs/README.md",
24
+ "bugs": "https://github.com/excom-dev/nucleus/issues",
25
+ "keywords": [
26
+ "dialog-anchor",
27
+ "neutron",
28
+ "custom-elements"
29
+ ],
30
+ "excom": {
31
+ "packageType": "kit-element"
32
+ },
33
+ "scripts": {
34
+ "build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
35
+ "build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
36
+ "format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
37
+ "test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
38
+ "coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
39
+ "dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
40
+ "preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
41
+ "build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
42
+ "build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
43
+ }
44
+ }
@@ -0,0 +1 @@
1
+ Caching has been disabled for this project's "apply-exports" command.
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1,19 @@
1
+ @import "@excom/listenable-element/src/index.css";
2
+
3
+ /**
4
+ * Styles for `<dialog-anchor>`.
5
+ * @element dialog-anchor
6
+ */
7
+
8
+ /* Aliases */
9
+ @custom-selector :--dialog-anchor dialog-anchor, .tag-dialog-anchor;
10
+
11
+ /**
12
+ * Skip the pointer cursor from the listenable mixin.
13
+ * @cssclass unstyled
14
+ */
15
+ @layer {
16
+ :--dialog-anchor:not(.unstyled) {
17
+ @mixin listenable-element;
18
+ }
19
+ }
@@ -0,0 +1,110 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "modules": [
4
+ {
5
+ "kind": "javascript-module",
6
+ "path": "dialog-anchor.ts",
7
+ "declarations": [
8
+ {
9
+ "kind": "class",
10
+ "name": "DialogAnchor",
11
+ "customElement": true,
12
+ "tagName": "dialog-anchor",
13
+ "mixins": [
14
+ {
15
+ "name": "ListenableElement",
16
+ "package": "@excom/listenable-element"
17
+ }
18
+ ],
19
+ "summary": "Open/close a `<dialog>` — by selector or nearest ancestor.",
20
+ "description": "Click target that opens or closes a `<dialog>` without any JavaScript. Wrap a button (or any clickable content) in `<dialog-anchor>` and point it at a `<dialog>` via `target-ref`, or let it find the nearest ancestor `<dialog>` automatically — the common case for a close button living inside the dialog it closes. By default it listens for `click`; set `listen-for` (from `ListenableElement`) to toggle on another event instead — e.g. auto-close a dialog when a nested `<super-form>` succeeds.",
21
+ "attributes": [
22
+ {
23
+ "name": "target-ref",
24
+ "type": {
25
+ "text": "string"
26
+ },
27
+ "description": "CSS selector for the `<dialog>` to toggle. If omitted, the element toggles the nearest ancestor `<dialog>`.",
28
+ "fieldName": "targetRef",
29
+ "values": [
30
+ "<CSS Selector>"
31
+ ]
32
+ },
33
+ {
34
+ "name": "is-modal",
35
+ "type": {
36
+ "text": "boolean"
37
+ },
38
+ "description": "Open the dialog as a modal (blocks the rest of the page) instead of a non-modal popover.",
39
+ "fieldName": "isModal"
40
+ }
41
+ ],
42
+ "members": [
43
+ {
44
+ "kind": "field",
45
+ "name": "targetRef",
46
+ "type": {
47
+ "text": "string"
48
+ },
49
+ "privacy": "public",
50
+ "readonly": false,
51
+ "description": "CSS selector for the `<dialog>` to toggle. If omitted, the element toggles the nearest ancestor `<dialog>`.",
52
+ "_neutron": {
53
+ "surface": "option"
54
+ }
55
+ },
56
+ {
57
+ "kind": "field",
58
+ "name": "isModal",
59
+ "type": {
60
+ "text": "boolean"
61
+ },
62
+ "privacy": "public",
63
+ "readonly": false,
64
+ "description": "Open the dialog as a modal (blocks the rest of the page) instead of a non-modal popover.",
65
+ "_neutron": {
66
+ "surface": "option"
67
+ }
68
+ }
69
+ ],
70
+ "_neutron": {
71
+ "cssClasses": [
72
+ {
73
+ "name": "unstyled",
74
+ "description": "Skip the pointer cursor from the listenable mixin."
75
+ }
76
+ ],
77
+ "cssAliases": [
78
+ {
79
+ "name": ":--dialog-anchor",
80
+ "selectors": [
81
+ "dialog-anchor",
82
+ ".tag-dialog-anchor"
83
+ ],
84
+ "kind": "element"
85
+ }
86
+ ]
87
+ }
88
+ }
89
+ ],
90
+ "exports": [
91
+ {
92
+ "kind": "js",
93
+ "name": "DialogAnchor",
94
+ "declaration": {
95
+ "name": "DialogAnchor",
96
+ "module": "dialog-anchor.ts"
97
+ }
98
+ },
99
+ {
100
+ "kind": "custom-element-definition",
101
+ "name": "dialog-anchor",
102
+ "declaration": {
103
+ "name": "DialogAnchor",
104
+ "module": "dialog-anchor.ts"
105
+ }
106
+ }
107
+ ]
108
+ }
109
+ ]
110
+ }
@@ -0,0 +1,11 @@
1
+ <div>
2
+ <dialog-anchor role="button" target-ref="#da-auto" is-modal>Open form</dialog-anchor>
3
+ <dialog id="da-auto">
4
+ <dialog-anchor listen-for="super-form-success">
5
+ <p>Simulating a form submit…</p>
6
+ <event-handler role="button" fire-event="super-form-success">
7
+ Submit
8
+ </event-handler>
9
+ </dialog-anchor>
10
+ </dialog>
11
+ </div>
@@ -0,0 +1,21 @@
1
+ <div>
2
+ <dialog-anchor role="button" target-ref="#da-modal" is-modal>
3
+ Open as modal
4
+ </dialog-anchor>
5
+ <dialog-anchor role="button" target-ref="#da-modal">
6
+ Open as non-modal
7
+ </dialog-anchor>
8
+ <dialog id="da-modal">
9
+ <article>
10
+ <header>
11
+ <dialog-anchor role="button" rel="prev"></dialog-anchor>
12
+ <h4>Modal dialogs block interaction with the rest of the page.</h4>
13
+ <p>This dialog is shown with Valence.css styling</p>
14
+ </header>
15
+ <p>Dialog content</p>
16
+ <footer>
17
+ Footer content
18
+ </footer>
19
+ </article>
20
+ </dialog>
21
+ </div>
@@ -0,0 +1,7 @@
1
+ <div>
2
+ <dialog-anchor role="button" target-ref="#da-simple" is-modal>Open dialog</dialog-anchor>
3
+ <dialog id="da-simple">
4
+ <p>A native, modal dialog.</p>
5
+ <dialog-anchor role="button">Close</dialog-anchor>
6
+ </dialog>
7
+ </div>
@@ -0,0 +1,163 @@
1
+ # dialog-anchor
2
+
3
+ Open and close a native `<dialog>` — no JavaScript required.
4
+
5
+
6
+ ```html
7
+ <div>
8
+ <dialog-anchor role="button" target-ref="#da-simple" is-modal>Open dialog</dialog-anchor>
9
+ <dialog id="da-simple">
10
+ <p>A native, modal dialog.</p>
11
+ <dialog-anchor role="button">Close</dialog-anchor>
12
+ </dialog>
13
+ </div>
14
+ ```
15
+
16
+
17
+ ## Features
18
+
19
+ - **Click to toggle** Opens / closes a `<dialog>` on click
20
+ - **Target or fallback** Point at any `<dialog>` via `target-ref`, or let
21
+ it find the nearest ancestor automatically (great for close buttons)
22
+ - **Modal or non-modal** `is-modal` blocks the rest of the page; omit it
23
+ for a lightweight, dismissible popover
24
+ - **Any trigger event** Inherits `listen-for` to open/close on custom
25
+ events instead of `click`
26
+
27
+ ## Installation
28
+
29
+
30
+ `@excom/dialog-anchor` v0.1.0
31
+
32
+ ```bash
33
+ pnpm add @excom/dialog-anchor
34
+ ```
35
+
36
+ ```bash
37
+ npm install @excom/dialog-anchor
38
+ ```
39
+
40
+ ```bash
41
+ yarn add @excom/dialog-anchor
42
+ ```
43
+
44
+ ### Import
45
+
46
+ ```ts
47
+ import "@excom/dialog-anchor";
48
+ ```
49
+
50
+ ```css
51
+ @import "@excom/dialog-anchor/dialog-anchor.css";
52
+ ```
53
+
54
+
55
+
56
+ ## Usage
57
+
58
+ Wrap a trigger in `<dialog-anchor>` and point it at a `<dialog>`. A
59
+ `<dialog-anchor>` with no `target-ref`, placed inside the `<dialog>` it
60
+ should close, needs no configuration at all.
61
+
62
+ ```html
63
+ <dialog-anchor target-ref="#confirm" role="button">Delete</dialog-anchor>
64
+ <dialog id="confirm">
65
+ <p>Are you sure?</p>
66
+ <dialog-anchor role="button">Cancel</dialog-anchor>
67
+ </dialog>
68
+ ```
69
+
70
+ ### API Reference
71
+
72
+
73
+ #### Attributes
74
+
75
+ | Name | Surface | Type | Default | Values | Description | Inherited from |
76
+ | --- | --- | --- | --- | --- | --- | --- |
77
+ | `target-ref` | option | `string` | | `<CSS Selector>` | CSS selector for the `<dialog>` to toggle. If omitted, the element toggles the nearest ancestor `<dialog>`. | |
78
+ | `is-modal` | option | `boolean` | | | Open the dialog as a modal (blocks the rest of the page) instead of a non-modal popover. | |
79
+ | `host-ref` | option | `string` | | `<CSS Selector>` \| `"window"` \| `"document"` \| `"html"` \| `"body"` \| `"head"` | Listen on another element / `window` / `document` — e.g. Escape to dismiss a dialog from a global `keydown`. Defaults to `:scope`. Used with `listen-for`. Not compatible with `listen-for-lifecycle`. The selector MUST resolve when `host-ref` is set — it will not wait for a match to appear. | `@excom/listenable-element` |
80
+ | `listen-for` | option | `tokenlist` | | `<EventName>…` | Space-separated event names to listen for. Defaults to `click` when unset (and no lifecycle list is set). | `@excom/listenable-element` |
81
+ | `listen-for-lifecycle` | option | `tokenlist` | | `"connected"` \| `"disconnected"` \| `"adopted"` | Space-separated element lifecycles to handle. | `@excom/listenable-element` |
82
+ | `listen-once` | option | `boolean` | | | Handle each distinct event name / lifecycle at most once. | `@excom/listenable-element` |
83
+ | `selector-filter` | option | `string` | | `<CSS Selector>` | Only handle events whose `event.target` matches this CSS selector. Does not support `:scope` in the selector. | `@excom/listenable-element` |
84
+ | `keycode-filter` | option | `tokenlist` | | `<key` \| `mod+key>…` | Space-separated key filters (OR). Join modifiers with `+` (AND, any order): `shift+k tab` → Shift+K or Tab. Modifiers: `shift`, `alt`, `ctrl`/`control`, `meta`. Case-insensitive. | `@excom/listenable-element` |
85
+ | `pathname-filter` | option | `tokenlist` | | `<pathname>…` | Only handle when `location.pathname` is one of these values — route-aware behaviors without a separate router element. | `@excom/listenable-element` |
86
+ | `prevent-default` | option | `boolean` | | | Call `preventDefault()` on matched events (ignored for lifecycles). | `@excom/listenable-element` |
87
+ | `stop-propagation` | option | `boolean` | | | Call `stopPropagation()` on matched events (ignored for lifecycles). | `@excom/listenable-element` |
88
+ | `stop-immediate-propagation` | option | `boolean` | | | Call `stopImmediatePropagation()` on matched events (ignored for lifecycles). | `@excom/listenable-element` |
89
+ | `vibrate-ms` | option | `number` | `"20 (when attribute is present with no value)"` | | Vibrate on handle (`navigator.vibrate`). Empty / `0` uses a 20ms pulse. | `@excom/listenable-element` |
90
+ | `delay-ms` | option | `number` | | | Delay handling by this many milliseconds. | `@excom/listenable-element` |
91
+ | `is-debounced` | option | `boolean` | | | With `delay-ms`, coalesce bursts into one trailing call (debounce). | `@excom/listenable-element` |
92
+
93
+ #### CSS Classes
94
+
95
+ | Name | Description |
96
+ | --- | --- |
97
+ | `.unstyled` | Skip the pointer cursor from the listenable mixin. |
98
+
99
+ #### CSS Aliases
100
+
101
+ | Alias | Kind | Matches | Description |
102
+ | --- | --- | --- | --- |
103
+ | `:--dialog-anchor` | element | `dialog-anchor`, `.tag-dialog-anchor` | |
104
+
105
+
106
+
107
+ ### Examples
108
+
109
+ #### Modal dialog
110
+
111
+ `is-modal` opens the dialog as a modal, blocking interaction with the rest
112
+ of the page until it's closed (using native `::backdrop`). Without that attribute,
113
+ the dialog is opened without a backdrop.
114
+
115
+ If using Valence.css, setting `.absolute` on the `dialog` element will display it `absolute`
116
+ in the surrounding content (as opposed to fixed).
117
+
118
+
119
+ ```html
120
+ <div>
121
+ <dialog-anchor role="button" target-ref="#da-modal" is-modal>
122
+ Open as modal
123
+ </dialog-anchor>
124
+ <dialog-anchor role="button" target-ref="#da-modal">
125
+ Open as non-modal
126
+ </dialog-anchor>
127
+ <dialog id="da-modal">
128
+ <article>
129
+ <header>
130
+ <dialog-anchor role="button" rel="prev"></dialog-anchor>
131
+ <h4>Modal dialogs block interaction with the rest of the page.</h4>
132
+ <p>This dialog is shown with Valence.css styling</p>
133
+ </header>
134
+ <p>Dialog content</p>
135
+ <footer>
136
+ Footer content
137
+ </footer>
138
+ </article>
139
+ </dialog>
140
+ </div>
141
+ ```
142
+
143
+
144
+ #### Close on a custom event
145
+
146
+ `listen-for` swaps the default `click` for any event. Pair it with a real
147
+ `<super-form>`'s `super-form-success` event to auto-close a dialog
148
+ once a form inside it succeeds — simulated here with `<event-handler>`.
149
+
150
+
151
+ ```html
152
+ <div>
153
+ <dialog-anchor role="button" target-ref="#da-auto" is-modal>Open form</dialog-anchor>
154
+ <dialog id="da-auto">
155
+ <dialog-anchor listen-for="super-form-success">
156
+ <p>Simulating a form submit…</p>
157
+ <event-handler role="button" fire-event="super-form-success">
158
+ Submit
159
+ </event-handler>
160
+ </dialog-anchor>
161
+ </dialog>
162
+ </div>
163
+ ```
@@ -0,0 +1 @@
1
+ Will probably deprecate this element now that `<button command commandfor>` and `event-handler[command-name]` cover invoking dialogs and popovers.
@@ -0,0 +1,58 @@
1
+ # dialog-anchor
2
+
3
+ Open and close a native `<dialog>` — no JavaScript required.
4
+
5
+ <include-content data-demo="simple"></include-content>
6
+
7
+ ## Features
8
+
9
+ - **Click to toggle** Opens / closes a `<dialog>` on click
10
+ - **Target or fallback** Point at any `<dialog>` via `target-ref`, or let
11
+ it find the nearest ancestor automatically (great for close buttons)
12
+ - **Modal or non-modal** `is-modal` blocks the rest of the page; omit it
13
+ for a lightweight, dismissible popover
14
+ - **Any trigger event** Inherits `listen-for` to open/close on custom
15
+ events instead of `click`
16
+
17
+ ## Installation
18
+
19
+ <include-content is-active template-ref="/views/install-section/install-section.html"></include-content>
20
+
21
+ ## Usage
22
+
23
+ Wrap a trigger in `<dialog-anchor>` and point it at a `<dialog>`. A
24
+ `<dialog-anchor>` with no `target-ref`, placed inside the `<dialog>` it
25
+ should close, needs no configuration at all.
26
+
27
+ ```html
28
+ <dialog-anchor target-ref="#confirm" role="button">Delete</dialog-anchor>
29
+ <dialog id="confirm">
30
+ <p>Are you sure?</p>
31
+ <dialog-anchor role="button">Cancel</dialog-anchor>
32
+ </dialog>
33
+ ```
34
+
35
+ ### API Reference
36
+
37
+ <include-content is-active template-ref="/views/api-reference/api-reference.html"></include-content>
38
+
39
+ ### Examples
40
+
41
+ #### Modal dialog
42
+
43
+ `is-modal` opens the dialog as a modal, blocking interaction with the rest
44
+ of the page until it's closed (using native `::backdrop`). Without that attribute,
45
+ the dialog is opened without a backdrop.
46
+
47
+ If using Valence.css, setting `.absolute` on the `dialog` element will display it `absolute`
48
+ in the surrounding content (as opposed to fixed).
49
+
50
+ <include-content data-demo="is-modal"></include-content>
51
+
52
+ #### Close on a custom event
53
+
54
+ `listen-for` swaps the default `click` for any event. Pair it with a real
55
+ `<super-form>`'s `super-form-success` event to auto-close a dialog
56
+ once a form inside it succeeds — simulated here with `<event-handler>`.
57
+
58
+ <include-content data-demo="auto-close"></include-content>
@@ -0,0 +1,199 @@
1
+ {
2
+ "shortName": "dialog-anchor",
3
+ "package": {
4
+ "name": "@excom/dialog-anchor",
5
+ "version": "0.1.0",
6
+ "description": "<dialog-anchor> custom element",
7
+ "peerDependencies": {},
8
+ "excom": {
9
+ "packageType": "kit-element"
10
+ }
11
+ },
12
+ "demos": {
13
+ "auto-close": "<div>\n <dialog-anchor role=\"button\" target-ref=\"#da-auto\" is-modal>Open form</dialog-anchor>\n <dialog id=\"da-auto\">\n <dialog-anchor listen-for=\"super-form-success\">\n <p>Simulating a form submit…</p>\n <event-handler role=\"button\" fire-event=\"super-form-success\">\n Submit\n </event-handler>\n </dialog-anchor>\n </dialog>\n</div>\n",
14
+ "is-modal": "<div>\n <dialog-anchor role=\"button\" target-ref=\"#da-modal\" is-modal>\n Open as modal\n </dialog-anchor>\n <dialog-anchor role=\"button\" target-ref=\"#da-modal\">\n Open as non-modal\n </dialog-anchor>\n <dialog id=\"da-modal\">\n <article>\n <header>\n <dialog-anchor role=\"button\" rel=\"prev\"></dialog-anchor>\n <h4>Modal dialogs block interaction with the rest of the page.</h4>\n <p>This dialog is shown with Valence.css styling</p>\n </header>\n <p>Dialog content</p>\n <footer>\n Footer content\n </footer>\n </article>\n </dialog>\n</div>",
15
+ "simple": "<div>\n <dialog-anchor role=\"button\" target-ref=\"#da-simple\" is-modal>Open dialog</dialog-anchor>\n <dialog id=\"da-simple\">\n <p>A native, modal dialog.</p>\n <dialog-anchor role=\"button\">Close</dialog-anchor>\n </dialog>\n</div>\n"
16
+ },
17
+ "readme": "<h1 id=\"md-dialog-anchor\">dialog-anchor</h1>\n<p>Open and close a native <code>&lt;dialog&gt;</code> — no JavaScript required.</p>\n<p><include-content data-demo=\"simple\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Click to toggle</strong> Opens / closes a <code>&lt;dialog&gt;</code> on click</li>\n<li><strong>Target or fallback</strong> Point at any <code>&lt;dialog&gt;</code> via <code>target-ref</code>, or let\nit find the nearest ancestor automatically (great for close buttons)</li>\n<li><strong>Modal or non-modal</strong> <code>is-modal</code> blocks the rest of the page; omit it\nfor a lightweight, dismissible popover</li>\n<li><strong>Any trigger event</strong> Inherits <code>listen-for</code> to open/close on custom\nevents instead of <code>click</code></li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<p>Wrap a trigger in <code>&lt;dialog-anchor&gt;</code> and point it at a <code>&lt;dialog&gt;</code>. A\n<code>&lt;dialog-anchor&gt;</code> with no <code>target-ref</code>, placed inside the <code>&lt;dialog&gt;</code> it\nshould close, needs no configuration at all.</p>\n<include-content data-language=\"html\"><template>&lt;dialog-anchor target-ref=\"#confirm\" role=\"button\"&gt;Delete&lt;/dialog-anchor&gt;\n&lt;dialog id=\"confirm\"&gt;\n &lt;p&gt;Are you sure?&lt;/p&gt;\n &lt;dialog-anchor role=\"button\"&gt;Cancel&lt;/dialog-anchor&gt;\n&lt;/dialog&gt;</template></include-content>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-modal-dialog\">Modal dialog</h4>\n<p><code>is-modal</code> opens the dialog as a modal, blocking interaction with the rest\nof the page until it&#39;s closed (using native <code>::backdrop</code>). Without that attribute,\nthe dialog is opened without a backdrop.</p>\n<p>If using Valence.css, setting <code>.absolute</code> on the <code>dialog</code> element will display it <code>absolute</code>\nin the surrounding content (as opposed to fixed).</p>\n<p><include-content data-demo=\"is-modal\"></include-content></p>\n<h4 id=\"md-close-on-a-custom-event\">Close on a custom event</h4>\n<p><code>listen-for</code> swaps the default <code>click</code> for any event. Pair it with a real\n<code>&lt;super-form&gt;</code>&#39;s <code>super-form-success</code> event to auto-close a dialog\nonce a form inside it succeeds — simulated here with <code>&lt;event-handler&gt;</code>.</p>\n<p><include-content data-demo=\"auto-close\"></include-content></p>\n",
18
+ "docs": {
19
+ "readme": "<h1 id=\"md-dialog-anchor\">dialog-anchor</h1>\n<p>Open and close a native <code>&lt;dialog&gt;</code> — no JavaScript required.</p>\n<p><include-content data-demo=\"simple\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Click to toggle</strong> Opens / closes a <code>&lt;dialog&gt;</code> on click</li>\n<li><strong>Target or fallback</strong> Point at any <code>&lt;dialog&gt;</code> via <code>target-ref</code>, or let\nit find the nearest ancestor automatically (great for close buttons)</li>\n<li><strong>Modal or non-modal</strong> <code>is-modal</code> blocks the rest of the page; omit it\nfor a lightweight, dismissible popover</li>\n<li><strong>Any trigger event</strong> Inherits <code>listen-for</code> to open/close on custom\nevents instead of <code>click</code></li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<p>Wrap a trigger in <code>&lt;dialog-anchor&gt;</code> and point it at a <code>&lt;dialog&gt;</code>. A\n<code>&lt;dialog-anchor&gt;</code> with no <code>target-ref</code>, placed inside the <code>&lt;dialog&gt;</code> it\nshould close, needs no configuration at all.</p>\n<include-content data-language=\"html\"><template>&lt;dialog-anchor target-ref=\"#confirm\" role=\"button\"&gt;Delete&lt;/dialog-anchor&gt;\n&lt;dialog id=\"confirm\"&gt;\n &lt;p&gt;Are you sure?&lt;/p&gt;\n &lt;dialog-anchor role=\"button\"&gt;Cancel&lt;/dialog-anchor&gt;\n&lt;/dialog&gt;</template></include-content>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-modal-dialog\">Modal dialog</h4>\n<p><code>is-modal</code> opens the dialog as a modal, blocking interaction with the rest\nof the page until it&#39;s closed (using native <code>::backdrop</code>). Without that attribute,\nthe dialog is opened without a backdrop.</p>\n<p>If using Valence.css, setting <code>.absolute</code> on the <code>dialog</code> element will display it <code>absolute</code>\nin the surrounding content (as opposed to fixed).</p>\n<p><include-content data-demo=\"is-modal\"></include-content></p>\n<h4 id=\"md-close-on-a-custom-event\">Close on a custom event</h4>\n<p><code>listen-for</code> swaps the default <code>click</code> for any event. Pair it with a real\n<code>&lt;super-form&gt;</code>&#39;s <code>super-form-success</code> event to auto-close a dialog\nonce a form inside it succeeds — simulated here with <code>&lt;event-handler&gt;</code>.</p>\n<p><include-content data-demo=\"auto-close\"></include-content></p>\n"
20
+ },
21
+ "installation": {
22
+ "name": "@excom/dialog-anchor",
23
+ "shortName": "dialog-anchor",
24
+ "version": "0.1.0",
25
+ "description": "<dialog-anchor> custom element",
26
+ "packageType": "kit-element",
27
+ "cdn": "<script src=\"https://unpkg.com/@excom/kit-utils/dist/index.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@excom/neutron/dist/index.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@excom/dialog-anchor@0.1.0/dist/index.umd.min.js\"></script>\n<link rel=\"stylesheet\" href=\"https://unpkg.com/@excom/dialog-anchor@0.1.0/dist/index.css\">",
28
+ "install": {
29
+ "npm": "npm install @excom/dialog-anchor"
30
+ },
31
+ "imports": {
32
+ "js": "import \"@excom/dialog-anchor\";",
33
+ "css": "@import \"@excom/dialog-anchor\";",
34
+ "html": "<!-- import path to `node_modules` will depend on your build setup -->\n<script type=\"module\" src=\"/node_modules/@excom/dialog-anchor\"></script>\n<link rel=\"stylesheet\" href=\"/node_modules/@excom/dialog-anchor\">"
35
+ },
36
+ "peerDependencies": []
37
+ },
38
+ "elementApis": [
39
+ {
40
+ "tag": "dialog-anchor",
41
+ "summary": "Open/close a <code>&lt;dialog&gt;</code> — by selector or nearest ancestor.",
42
+ "kind": "class",
43
+ "attributes": [
44
+ {
45
+ "name": "delay-ms",
46
+ "type": "number",
47
+ "description": "Delay handling by this many milliseconds.",
48
+ "fieldName": "delayMs",
49
+ "surface": "option",
50
+ "inheritedFrom": "@excom/listenable-element"
51
+ },
52
+ {
53
+ "name": "host-ref",
54
+ "type": "string",
55
+ "description": "Listen on another element / <code>window</code> / <code>document</code> — e.g. Escape to dismiss a dialog from a global <code>keydown</code>. Defaults to <code>:scope</code>. Used with <code>listen-for</code>. Not compatible with <code>listen-for-lifecycle</code>. The selector MUST resolve when <code>host-ref</code> is set — it will not wait for a match to appear.",
56
+ "fieldName": "hostRef",
57
+ "surface": "option",
58
+ "values": "<CSS Selector> | \"window\" | \"document\" | \"html\" | \"body\" | \"head\"",
59
+ "inheritedFrom": "@excom/listenable-element"
60
+ },
61
+ {
62
+ "name": "is-debounced",
63
+ "type": "boolean",
64
+ "description": "With <code>delay-ms</code>, coalesce bursts into one trailing call (debounce).",
65
+ "fieldName": "isDebounced",
66
+ "surface": "option",
67
+ "inheritedFrom": "@excom/listenable-element"
68
+ },
69
+ {
70
+ "name": "is-modal",
71
+ "type": "boolean",
72
+ "description": "Open the dialog as a modal (blocks the rest of the page) instead of a non-modal popover.",
73
+ "fieldName": "isModal",
74
+ "surface": "option"
75
+ },
76
+ {
77
+ "name": "keycode-filter",
78
+ "type": "tokenlist",
79
+ "description": "Space-separated key filters (OR). Join modifiers with <code>+</code> (AND, any order): <code>shift+k tab</code> → Shift+K or Tab. Modifiers: <code>shift</code>, <code>alt</code>, <code>ctrl</code>/<code>control</code>, <code>meta</code>. Case-insensitive.",
80
+ "fieldName": "keycodeFilter",
81
+ "surface": "option",
82
+ "values": "<key | mod+key>…",
83
+ "inheritedFrom": "@excom/listenable-element"
84
+ },
85
+ {
86
+ "name": "listen-for",
87
+ "type": "tokenlist",
88
+ "description": "Space-separated event names to listen for. Defaults to <code>click</code> when unset (and no lifecycle list is set).",
89
+ "fieldName": "listenFor",
90
+ "surface": "option",
91
+ "values": "<EventName>…",
92
+ "inheritedFrom": "@excom/listenable-element"
93
+ },
94
+ {
95
+ "name": "listen-for-lifecycle",
96
+ "type": "tokenlist",
97
+ "description": "Space-separated element lifecycles to handle.",
98
+ "fieldName": "listenForLifecycle",
99
+ "surface": "option",
100
+ "values": "\"connected\" | \"disconnected\" | \"adopted\"",
101
+ "inheritedFrom": "@excom/listenable-element"
102
+ },
103
+ {
104
+ "name": "listen-once",
105
+ "type": "boolean",
106
+ "description": "Handle each distinct event name / lifecycle at most once.",
107
+ "fieldName": "listenOnce",
108
+ "surface": "option",
109
+ "inheritedFrom": "@excom/listenable-element"
110
+ },
111
+ {
112
+ "name": "pathname-filter",
113
+ "type": "tokenlist",
114
+ "description": "Only handle when <code>location.pathname</code> is one of these values — route-aware behaviors without a separate router element.",
115
+ "fieldName": "pathnameFilter",
116
+ "surface": "option",
117
+ "values": "<pathname>…",
118
+ "inheritedFrom": "@excom/listenable-element"
119
+ },
120
+ {
121
+ "name": "prevent-default",
122
+ "type": "boolean",
123
+ "description": "Call <code>preventDefault()</code> on matched events (ignored for lifecycles).",
124
+ "fieldName": "preventDefault",
125
+ "surface": "option",
126
+ "inheritedFrom": "@excom/listenable-element"
127
+ },
128
+ {
129
+ "name": "selector-filter",
130
+ "type": "string",
131
+ "description": "Only handle events whose <code>event.target</code> matches this CSS selector. Does not support <code>:scope</code> in the selector.",
132
+ "fieldName": "selectorFilter",
133
+ "surface": "option",
134
+ "values": "<CSS Selector>",
135
+ "inheritedFrom": "@excom/listenable-element"
136
+ },
137
+ {
138
+ "name": "stop-immediate-propagation",
139
+ "type": "boolean",
140
+ "description": "Call <code>stopImmediatePropagation()</code> on matched events (ignored for lifecycles).",
141
+ "fieldName": "stopImmediatePropagation",
142
+ "surface": "option",
143
+ "inheritedFrom": "@excom/listenable-element"
144
+ },
145
+ {
146
+ "name": "stop-propagation",
147
+ "type": "boolean",
148
+ "description": "Call <code>stopPropagation()</code> on matched events (ignored for lifecycles).",
149
+ "fieldName": "stopPropagation",
150
+ "surface": "option",
151
+ "inheritedFrom": "@excom/listenable-element"
152
+ },
153
+ {
154
+ "name": "target-ref",
155
+ "type": "string",
156
+ "description": "CSS selector for the <code>&lt;dialog&gt;</code> to toggle. If omitted, the element toggles the nearest ancestor <code>&lt;dialog&gt;</code>.",
157
+ "fieldName": "targetRef",
158
+ "surface": "option",
159
+ "values": "<CSS Selector>"
160
+ },
161
+ {
162
+ "name": "vibrate-ms",
163
+ "type": "number",
164
+ "description": "Vibrate on handle (<code>navigator.vibrate</code>). Empty / <code>0</code> uses a 20ms pulse.",
165
+ "fieldName": "vibrateMs",
166
+ "surface": "option",
167
+ "default": "\"20 (when attribute is present with no value)\"",
168
+ "inheritedFrom": "@excom/listenable-element"
169
+ }
170
+ ],
171
+ "events": [],
172
+ "slots": [],
173
+ "cssProperties": [],
174
+ "cssClasses": [
175
+ {
176
+ "name": "unstyled",
177
+ "description": "Skip the pointer cursor from the listenable mixin."
178
+ }
179
+ ],
180
+ "cssAliases": [
181
+ {
182
+ "name": ":--dialog-anchor",
183
+ "selectors": [
184
+ "dialog-anchor",
185
+ ".tag-dialog-anchor"
186
+ ],
187
+ "kind": "element",
188
+ "description": ""
189
+ }
190
+ ],
191
+ "listens": [],
192
+ "commands": [],
193
+ "defaultActions": [],
194
+ "expectedChildren": [],
195
+ "provisions": []
196
+ }
197
+ ],
198
+ "exportedFiles": {}
199
+ }
@@ -0,0 +1,31 @@
1
+ import "@excom/event-handler";
2
+ import "../../index";
3
+ import {
4
+ afterEach,
5
+ describe,
6
+ expect,
7
+ it,
8
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
9
+ import {
10
+ click,
11
+ flush,
12
+ mountView,
13
+ readDemo,
14
+ } from "@excom/quark/support/tests/view-helpers";
15
+
16
+ describe("auto-close view", () => {
17
+ afterEach(() => {
18
+ document.body.innerHTML = "";
19
+ });
20
+
21
+ it("closes the dialog on simulated submit success", async () => {
22
+ const { root } = await mountView(readDemo(import.meta.url, "auto-close"));
23
+ const dialog = root.querySelector("dialog")!;
24
+ click(root.querySelector("[target-ref]"));
25
+ await flush();
26
+ expect(dialog.open).toBe(true);
27
+ click(dialog.querySelector("[role='button']"));
28
+ await flush();
29
+ expect(dialog.open).toBe(false);
30
+ });
31
+ });
@@ -0,0 +1,102 @@
1
+ import {
2
+ afterEach,
3
+ describe,
4
+ expect,
5
+ fixture,
6
+ it,
7
+ vi,
8
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
9
+ import "../../index";
10
+
11
+ describe("dialog-anchor", () => {
12
+ afterEach(() => {
13
+ document.body.innerHTML = "";
14
+ vi.restoreAllMocks();
15
+ });
16
+
17
+ it("opens and closes dialog properly", async () => {
18
+ fixture<HTMLDivElement>(
19
+ `<div>
20
+ <dialog-anchor target-ref="dialog" id="c"></dialog-anchor>
21
+ </div>
22
+ <dialog>
23
+ <dialog-anchor id="b"></dialog-anchor>
24
+ </dialog>
25
+ <dialog-anchor is-modal target-ref="dialog:has(+:scope)" id="a"></dialog-anchor>`,
26
+ );
27
+ const dialogAnchor1 = document.querySelector(
28
+ "#a",
29
+ ) as HTMLDialogAnchorElement;
30
+ const dialogAnchor2 = document.querySelector(
31
+ "#b",
32
+ ) as HTMLDialogAnchorElement;
33
+ const dialogAnchor3 = document.querySelector(
34
+ "#c",
35
+ ) as HTMLDialogAnchorElement;
36
+ const dialog = document.querySelector("dialog")!;
37
+ const showSpy = vi.spyOn(dialog, "show");
38
+ const showModalSpy = vi.spyOn(dialog, "showModal");
39
+ const closeSpy = vi.spyOn(dialog, "close");
40
+
41
+ expect(dialog.open).toBe(false);
42
+
43
+ // modal, previous sibling
44
+ dialogAnchor1.dispatchEvent(new Event("click"));
45
+ expect(showModalSpy).toHaveBeenCalled();
46
+ expect(showSpy).not.toHaveBeenCalled();
47
+ expect(closeSpy).not.toHaveBeenCalled();
48
+
49
+ // close, child
50
+ dialogAnchor2.dispatchEvent(new Event("click"));
51
+ expect(closeSpy).toHaveBeenCalled();
52
+
53
+ // non-modal, target-ref
54
+ dialogAnchor3.dispatchEvent(new Event("click"));
55
+ expect(showSpy).toHaveBeenCalled();
56
+ expect(dialog.open).toBe(true);
57
+
58
+ // toggle
59
+ dialogAnchor3.dispatchEvent(new Event("click"));
60
+ expect(closeSpy).toHaveBeenCalledTimes(2);
61
+ });
62
+ });
63
+
64
+ describe("dialog-anchor (no dialog)", () => {
65
+ afterEach(() => {
66
+ document.body.innerHTML = "";
67
+ vi.restoreAllMocks();
68
+ });
69
+
70
+ it("does nothing when neither target-ref nor an ancestor resolves to a dialog", () => {
71
+ const wrapper = fixture<HTMLDivElement>(
72
+ `<div>
73
+ <p id="not-a-dialog"></p>
74
+ <dialog-anchor target-ref="#not-a-dialog" id="x"></dialog-anchor>
75
+ <dialog-anchor target-ref="#missing" id="y"></dialog-anchor>
76
+ <dialog-anchor id="z"></dialog-anchor>
77
+ </div>`,
78
+ );
79
+ const showSpy = vi.spyOn(HTMLDialogElement.prototype, "show");
80
+ const showModalSpy = vi.spyOn(HTMLDialogElement.prototype, "showModal");
81
+ const closeSpy = vi.spyOn(HTMLDialogElement.prototype, "close");
82
+ wrapper.querySelectorAll("dialog-anchor").forEach((anchor) => {
83
+ expect(() => anchor.dispatchEvent(new Event("click"))).not.toThrow();
84
+ });
85
+ expect(showSpy).not.toHaveBeenCalled();
86
+ expect(showModalSpy).not.toHaveBeenCalled();
87
+ expect(closeSpy).not.toHaveBeenCalled();
88
+ });
89
+
90
+ it("falls back to the closest dialog when target-ref is not a dialog", () => {
91
+ fixture<HTMLDialogElement>(
92
+ `<dialog>
93
+ <p id="inner-p"></p>
94
+ <dialog-anchor target-ref="#inner-p" is-modal></dialog-anchor>
95
+ </dialog>`,
96
+ );
97
+ const dialog = document.querySelector("dialog")!;
98
+ const showModalSpy = vi.spyOn(dialog, "showModal");
99
+ document.querySelector("dialog-anchor")!.dispatchEvent(new Event("click"));
100
+ expect(showModalSpy).toHaveBeenCalledTimes(1);
101
+ });
102
+ });
@@ -0,0 +1,34 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
8
+ import {
9
+ click,
10
+ flush,
11
+ mountView,
12
+ readDemo,
13
+ } from "@excom/quark/support/tests/view-helpers";
14
+
15
+ describe("is-modal view", () => {
16
+ afterEach(() => {
17
+ document.body.innerHTML = "";
18
+ });
19
+
20
+ it("opens modal and non-modal", async () => {
21
+ const { root } = await mountView(readDemo(import.meta.url, "is-modal"));
22
+ const dialog = root.querySelector("dialog")!;
23
+ const [modalBtn, modelessBtn] = root.querySelectorAll(
24
+ "dialog-anchor[target-ref]",
25
+ );
26
+ click(modalBtn);
27
+ await flush();
28
+ expect(dialog.open).toBe(true);
29
+ dialog.close();
30
+ click(modelessBtn);
31
+ await flush();
32
+ expect(dialog.open).toBe(true);
33
+ });
34
+ });
@@ -0,0 +1,31 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
8
+ import {
9
+ click,
10
+ flush,
11
+ mountView,
12
+ readDemo,
13
+ } from "@excom/quark/support/tests/view-helpers";
14
+
15
+ describe("simple view", () => {
16
+ afterEach(() => {
17
+ document.body.innerHTML = "";
18
+ });
19
+
20
+ it("opens and closes the dialog", async () => {
21
+ const { root } = await mountView(readDemo(import.meta.url, "simple"));
22
+ const dialog = root.querySelector("dialog")!;
23
+ expect(dialog.open).toBe(false);
24
+ click(root.querySelector("[target-ref]"));
25
+ await flush();
26
+ expect(dialog.open).toBe(true);
27
+ click(dialog.querySelector("dialog-anchor"));
28
+ await flush();
29
+ expect(dialog.open).toBe(false);
30
+ });
31
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "@excom/heft-rig/profiles/default/config/tsconfig.json",
3
+ "include": ["./*.ts"],
4
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
5
+ }