foldkit 0.118.0 → 0.120.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/dom/dom.d.ts +41 -7
- package/dist/dom/dom.d.ts.map +1 -1
- package/dist/dom/dom.js +76 -28
- package/dist/dom/index.d.ts +1 -1
- package/dist/dom/index.d.ts.map +1 -1
- package/dist/dom/index.js +1 -1
- package/dist/dom/public.d.ts +1 -1
- package/dist/dom/public.d.ts.map +1 -1
- package/dist/dom/public.js +1 -1
- package/dist/html/boundary.d.ts +14 -0
- package/dist/html/boundary.d.ts.map +1 -1
- package/dist/html/boundary.js +33 -0
- package/dist/html/childAttribute.d.ts +6 -0
- package/dist/html/childAttribute.d.ts.map +1 -1
- package/dist/html/childAttribute.js +3 -1
- package/dist/html/index.d.ts +660 -0
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +228 -2
- package/dist/html/runtimeSingleton.d.ts +9 -0
- package/dist/html/runtimeSingleton.d.ts.map +1 -1
- package/dist/html/runtimeSingleton.js +15 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +5 -2
- package/dist/subscription/fromEvent.d.ts +59 -0
- package/dist/subscription/fromEvent.d.ts.map +1 -0
- package/dist/subscription/fromEvent.js +50 -0
- package/dist/subscription/public.d.ts +2 -0
- package/dist/subscription/public.d.ts.map +1 -1
- package/dist/subscription/public.js +1 -0
- package/dist/test/apps/svgAttributes.d.ts +11 -0
- package/dist/test/apps/svgAttributes.d.ts.map +1 -0
- package/dist/test/apps/svgAttributes.js +106 -0
- package/package.json +3 -2
package/dist/dom/dom.d.ts
CHANGED
|
@@ -48,31 +48,65 @@ export declare const focus: (selector: string, options?: Readonly<{
|
|
|
48
48
|
*
|
|
49
49
|
* Pass `focusSelector` to focus an element inside the dialog when it opens.
|
|
50
50
|
*
|
|
51
|
-
* Records the element that had focus when the dialog opened so `
|
|
51
|
+
* Records the element that had focus when the dialog opened so `closeDialog`
|
|
52
52
|
* can return focus there, the way `showModal()` would natively.
|
|
53
53
|
*
|
|
54
54
|
* @example
|
|
55
55
|
* ```typescript
|
|
56
|
-
* Dom.
|
|
57
|
-
* Dom.
|
|
56
|
+
* Dom.showDialog('#my-dialog')
|
|
57
|
+
* Dom.showDialog('#my-dialog', { focusSelector: '#search-input' })
|
|
58
58
|
* ```
|
|
59
59
|
*/
|
|
60
|
-
export declare const
|
|
60
|
+
export declare const showDialog: (selector: string, options?: Readonly<{
|
|
61
61
|
focusSelector?: string;
|
|
62
62
|
}>) => Effect.Effect<void, ElementNotFound>;
|
|
63
63
|
/**
|
|
64
64
|
* Closes a dialog element using `.close()`.
|
|
65
|
-
* Cleans up the keyboard handlers installed by `
|
|
65
|
+
* Cleans up the keyboard handlers installed by `showDialog` and restores focus to
|
|
66
66
|
* the element that was focused before the dialog opened (the trigger, or the
|
|
67
67
|
* dialog beneath it when closing a stacked dialog).
|
|
68
68
|
* Fails with `ElementNotFound` if the selector does not match an `HTMLDialogElement`.
|
|
69
69
|
*
|
|
70
70
|
* @example
|
|
71
71
|
* ```typescript
|
|
72
|
-
* Dom.
|
|
72
|
+
* Dom.closeDialog('#my-dialog')
|
|
73
73
|
* ```
|
|
74
74
|
*/
|
|
75
|
-
export declare const
|
|
75
|
+
export declare const closeDialog: (selector: string) => Effect.Effect<void, ElementNotFound>;
|
|
76
|
+
/**
|
|
77
|
+
* Releases the framework hygiene a dialog holds while open: the focus-trap
|
|
78
|
+
* keyboard handler, the recorded return focus, the dialog stack entry, the
|
|
79
|
+
* z-index counter, and one page scroll lock. Use this as a backstop for the
|
|
80
|
+
* case where a dialog's element is removed from the DOM without a purposeful
|
|
81
|
+
* close, the classic example being navigation away from a route-keyed subtree
|
|
82
|
+
* that contains the dialog. The normal close path (`closeDialog` plus the
|
|
83
|
+
* Dialog component's `unlockScroll` Command) already releases these, so this
|
|
84
|
+
* is the missing teardown when no close Message ever flows through `update`.
|
|
85
|
+
*
|
|
86
|
+
* Addressed by the dialog's id, not a selector, because the element is
|
|
87
|
+
* typically already gone from the DOM by the time this runs (that is the whole
|
|
88
|
+
* point of the backstop). The hygiene installed by `showDialog` is tracked by
|
|
89
|
+
* id so it can be reclaimed without a live element handle. The id must be
|
|
90
|
+
* non-empty and unique within the document, since it keys this cleanup
|
|
91
|
+
* accounting; a duplicate or empty id would release the wrong dialog's hygiene.
|
|
92
|
+
*
|
|
93
|
+
* Idempotent and exactly-once. It releases only when the dialog currently
|
|
94
|
+
* holds hygiene, then clears the per-dialog marker, so calling it after a
|
|
95
|
+
* normal close, or twice, is a no-op that never under-counts the shared
|
|
96
|
+
* scroll lock. Carries no application close semantics: the Dialog component
|
|
97
|
+
* owns the user-facing close (animation, `Closed` OutMessage, consumer
|
|
98
|
+
* Commands); this only reclaims framework resources.
|
|
99
|
+
*
|
|
100
|
+
* Resolves to `true` when it released resources, `false` when there was
|
|
101
|
+
* nothing to release. Never fails: an id with no held hygiene is a no-op,
|
|
102
|
+
* since the goal is reclaiming resources that may already be gone.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* Dom.releaseDialogResources('my-dialog')
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export declare const releaseDialogResources: (id: string) => Effect.Effect<boolean>;
|
|
76
110
|
/**
|
|
77
111
|
* Programmatically clicks an element matching the given selector.
|
|
78
112
|
* Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
|
package/dist/dom/dom.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/dom/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAGf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/dom/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAGf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA4C5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,KAAK,GAChB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,KACvE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAQlC,CAAA;AAEJ;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAyElC,CAAA;AAuBJ;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,GACtB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CASlC,CAAA;AAqBJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,sBAAsB,GAAI,IAAI,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAOrE,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,wBAAwB,GACnC,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAcJ;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,0BAA0B,GACrC,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IACjB,KAAK,CAAC,EAAE,qBAAqB,CAAA;IAC7B,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC1B,CAAC,KACD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAuBlC,CAAA;AAEJ,yEAAyE;AACzE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAA;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,WAAW,cAAc,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAiClC,CAAA"}
|
package/dist/dom/dom.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Array, Effect, Equal, Function, Match as M, Number, Option, } from 'effect';
|
|
2
2
|
import { afterCommit, afterPaint } from '../render/render.js';
|
|
3
3
|
import { ElementNotFound } from './error.js';
|
|
4
|
+
import { unlockScroll } from './scrollLock.js';
|
|
4
5
|
const BASE_DIALOG_Z_INDEX = 2147483600;
|
|
5
6
|
let openDialogCount = 0;
|
|
6
|
-
const
|
|
7
|
-
const dialogReturnFocus = new WeakMap();
|
|
7
|
+
const dialogHygieneById = new Map();
|
|
8
8
|
let openDialogStack = [];
|
|
9
9
|
const FOCUSABLE_SELECTOR = Array.join([
|
|
10
10
|
'a[href]:not([tabindex="-1"])',
|
|
@@ -72,37 +72,38 @@ export const focus = (selector, options) => Effect.gen(function* () {
|
|
|
72
72
|
*
|
|
73
73
|
* Pass `focusSelector` to focus an element inside the dialog when it opens.
|
|
74
74
|
*
|
|
75
|
-
* Records the element that had focus when the dialog opened so `
|
|
75
|
+
* Records the element that had focus when the dialog opened so `closeDialog`
|
|
76
76
|
* can return focus there, the way `showModal()` would natively.
|
|
77
77
|
*
|
|
78
78
|
* @example
|
|
79
79
|
* ```typescript
|
|
80
|
-
* Dom.
|
|
81
|
-
* Dom.
|
|
80
|
+
* Dom.showDialog('#my-dialog')
|
|
81
|
+
* Dom.showDialog('#my-dialog', { focusSelector: '#search-input' })
|
|
82
82
|
* ```
|
|
83
83
|
*/
|
|
84
|
-
export const
|
|
84
|
+
export const showDialog = (selector, options) => Effect.gen(function* () {
|
|
85
85
|
yield* afterCommit;
|
|
86
86
|
const element = document.querySelector(selector);
|
|
87
87
|
if (!(element instanceof HTMLDialogElement)) {
|
|
88
88
|
return yield* Effect.fail(new ElementNotFound({ selector }));
|
|
89
89
|
}
|
|
90
|
+
const { id } = element;
|
|
90
91
|
element.style.position = 'fixed';
|
|
91
92
|
element.style.inset = '0';
|
|
92
93
|
openDialogCount++;
|
|
93
94
|
element.style.zIndex = String(BASE_DIALOG_Z_INDEX + openDialogCount);
|
|
94
95
|
const previouslyFocused = document.activeElement;
|
|
95
|
-
|
|
96
|
-
previouslyFocused !== document.body
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
const returnFocus = previouslyFocused instanceof HTMLElement &&
|
|
97
|
+
previouslyFocused !== document.body
|
|
98
|
+
? previouslyFocused
|
|
99
|
+
: undefined;
|
|
99
100
|
element.show();
|
|
100
|
-
openDialogStack = Array.append(Array.filter(openDialogStack, dialog => dialog !== element), element);
|
|
101
|
+
openDialogStack = Array.append(Array.filter(openDialogStack, dialog => dialog.element !== element), { id, element });
|
|
101
102
|
const handleKeydown = (event) => {
|
|
102
103
|
if (!element.open) {
|
|
103
104
|
return;
|
|
104
105
|
}
|
|
105
|
-
const isTopmost = Option.exists(Array.last(openDialogStack), topmost => topmost === element);
|
|
106
|
+
const isTopmost = Option.exists(Array.last(openDialogStack), topmost => topmost.element === element);
|
|
106
107
|
if (!isTopmost) {
|
|
107
108
|
return;
|
|
108
109
|
}
|
|
@@ -117,7 +118,10 @@ export const showModal = (selector, options) => Effect.gen(function* () {
|
|
|
117
118
|
}), M.orElse(Function.constVoid));
|
|
118
119
|
};
|
|
119
120
|
document.addEventListener('keydown', handleKeydown);
|
|
120
|
-
|
|
121
|
+
dialogHygieneById.set(id, {
|
|
122
|
+
removeKeydownListener: () => document.removeEventListener('keydown', handleKeydown),
|
|
123
|
+
returnFocus,
|
|
124
|
+
});
|
|
121
125
|
if (options?.focusSelector) {
|
|
122
126
|
const focusTarget = element.querySelector(options.focusSelector);
|
|
123
127
|
if (focusTarget instanceof HTMLElement) {
|
|
@@ -142,36 +146,80 @@ const trapFocusWithinDialog = (event, dialog) => {
|
|
|
142
146
|
};
|
|
143
147
|
/**
|
|
144
148
|
* Closes a dialog element using `.close()`.
|
|
145
|
-
* Cleans up the keyboard handlers installed by `
|
|
149
|
+
* Cleans up the keyboard handlers installed by `showDialog` and restores focus to
|
|
146
150
|
* the element that was focused before the dialog opened (the trigger, or the
|
|
147
151
|
* dialog beneath it when closing a stacked dialog).
|
|
148
152
|
* Fails with `ElementNotFound` if the selector does not match an `HTMLDialogElement`.
|
|
149
153
|
*
|
|
150
154
|
* @example
|
|
151
155
|
* ```typescript
|
|
152
|
-
* Dom.
|
|
156
|
+
* Dom.closeDialog('#my-dialog')
|
|
153
157
|
* ```
|
|
154
158
|
*/
|
|
155
|
-
export const
|
|
159
|
+
export const closeDialog = (selector) => Effect.suspend(() => {
|
|
156
160
|
const element = document.querySelector(selector);
|
|
157
161
|
if (element instanceof HTMLDialogElement) {
|
|
158
162
|
element.close();
|
|
159
|
-
|
|
160
|
-
openDialogCount = Math.max(0, openDialogCount - 1);
|
|
161
|
-
const cleanup = dialogCleanups.get(element);
|
|
162
|
-
if (cleanup) {
|
|
163
|
-
cleanup();
|
|
164
|
-
dialogCleanups.delete(element);
|
|
165
|
-
}
|
|
166
|
-
const returnFocus = dialogReturnFocus.get(element);
|
|
167
|
-
dialogReturnFocus.delete(element);
|
|
168
|
-
if (returnFocus !== undefined && document.contains(returnFocus)) {
|
|
169
|
-
returnFocus.focus();
|
|
170
|
-
}
|
|
163
|
+
releaseDialogHygieneById(element.id);
|
|
171
164
|
return Effect.void;
|
|
172
165
|
}
|
|
173
166
|
return Effect.fail(new ElementNotFound({ selector }));
|
|
174
167
|
});
|
|
168
|
+
const releaseDialogHygieneById = (id) => {
|
|
169
|
+
const hygiene = dialogHygieneById.get(id);
|
|
170
|
+
if (hygiene === undefined) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
openDialogStack = Array.filter(openDialogStack, dialog => dialog.id !== id);
|
|
174
|
+
openDialogCount = Math.max(0, Number.decrement(openDialogCount));
|
|
175
|
+
hygiene.removeKeydownListener();
|
|
176
|
+
dialogHygieneById.delete(id);
|
|
177
|
+
const { returnFocus } = hygiene;
|
|
178
|
+
if (returnFocus !== undefined && document.contains(returnFocus)) {
|
|
179
|
+
returnFocus.focus();
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Releases the framework hygiene a dialog holds while open: the focus-trap
|
|
185
|
+
* keyboard handler, the recorded return focus, the dialog stack entry, the
|
|
186
|
+
* z-index counter, and one page scroll lock. Use this as a backstop for the
|
|
187
|
+
* case where a dialog's element is removed from the DOM without a purposeful
|
|
188
|
+
* close, the classic example being navigation away from a route-keyed subtree
|
|
189
|
+
* that contains the dialog. The normal close path (`closeDialog` plus the
|
|
190
|
+
* Dialog component's `unlockScroll` Command) already releases these, so this
|
|
191
|
+
* is the missing teardown when no close Message ever flows through `update`.
|
|
192
|
+
*
|
|
193
|
+
* Addressed by the dialog's id, not a selector, because the element is
|
|
194
|
+
* typically already gone from the DOM by the time this runs (that is the whole
|
|
195
|
+
* point of the backstop). The hygiene installed by `showDialog` is tracked by
|
|
196
|
+
* id so it can be reclaimed without a live element handle. The id must be
|
|
197
|
+
* non-empty and unique within the document, since it keys this cleanup
|
|
198
|
+
* accounting; a duplicate or empty id would release the wrong dialog's hygiene.
|
|
199
|
+
*
|
|
200
|
+
* Idempotent and exactly-once. It releases only when the dialog currently
|
|
201
|
+
* holds hygiene, then clears the per-dialog marker, so calling it after a
|
|
202
|
+
* normal close, or twice, is a no-op that never under-counts the shared
|
|
203
|
+
* scroll lock. Carries no application close semantics: the Dialog component
|
|
204
|
+
* owns the user-facing close (animation, `Closed` OutMessage, consumer
|
|
205
|
+
* Commands); this only reclaims framework resources.
|
|
206
|
+
*
|
|
207
|
+
* Resolves to `true` when it released resources, `false` when there was
|
|
208
|
+
* nothing to release. Never fails: an id with no held hygiene is a no-op,
|
|
209
|
+
* since the goal is reclaiming resources that may already be gone.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* Dom.releaseDialogResources('my-dialog')
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export const releaseDialogResources = (id) => Effect.suspend(() => {
|
|
217
|
+
const released = releaseDialogHygieneById(id);
|
|
218
|
+
if (released) {
|
|
219
|
+
return Effect.as(unlockScroll, true);
|
|
220
|
+
}
|
|
221
|
+
return Effect.succeed(false);
|
|
222
|
+
});
|
|
175
223
|
/**
|
|
176
224
|
* Programmatically clicks an element matching the given selector.
|
|
177
225
|
* Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
|
package/dist/dom/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ElementNotFound } from './error.js';
|
|
2
|
-
export { advanceFocus, clickElement,
|
|
2
|
+
export { advanceFocus, clickElement, closeDialog, focus, releaseDialogResources, scrollIntoView, scrollIntoViewAfterPaint, scrollIntoViewIfNotVisible, showDialog, } from './dom.js';
|
|
3
3
|
export type { FocusDirection } from './dom.js';
|
|
4
4
|
export { detectElementMovement } from './elementMovement.js';
|
|
5
5
|
export { inertOthers, restoreInert } from './inert.js';
|
package/dist/dom/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dom/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dom/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,KAAK,EACL,sBAAsB,EACtB,cAAc,EACd,wBAAwB,EACxB,0BAA0B,EAC1B,UAAU,GACX,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA"}
|
package/dist/dom/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ElementNotFound } from './error.js';
|
|
2
|
-
export { advanceFocus, clickElement,
|
|
2
|
+
export { advanceFocus, clickElement, closeDialog, focus, releaseDialogResources, scrollIntoView, scrollIntoViewAfterPaint, scrollIntoViewIfNotVisible, showDialog, } from './dom.js';
|
|
3
3
|
export { detectElementMovement } from './elementMovement.js';
|
|
4
4
|
export { inertOthers, restoreInert } from './inert.js';
|
|
5
5
|
export { lockScroll, unlockScroll } from './scrollLock.js';
|
package/dist/dom/public.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ElementNotFound, advanceFocus, clickElement,
|
|
1
|
+
export { ElementNotFound, advanceFocus, clickElement, closeDialog, detectElementMovement, focus, inertOthers, lockScroll, releaseDialogResources, restoreInert, scrollIntoView, scrollIntoViewAfterPaint, scrollIntoViewIfNotVisible, showDialog, unlockScroll, waitForAnimationSettled, } from './index.js';
|
|
2
2
|
export type { FocusDirection } from './index.js';
|
|
3
3
|
//# sourceMappingURL=public.d.ts.map
|
package/dist/dom/public.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/dom/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/dom/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,KAAK,EACL,WAAW,EACX,UAAU,EACV,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,wBAAwB,EACxB,0BAA0B,EAC1B,UAAU,EACV,YAAY,EACZ,uBAAuB,GACxB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/dom/public.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ElementNotFound, advanceFocus, clickElement,
|
|
1
|
+
export { ElementNotFound, advanceFocus, clickElement, closeDialog, detectElementMovement, focus, inertOthers, lockScroll, releaseDialogResources, restoreInert, scrollIntoView, scrollIntoViewAfterPaint, scrollIntoViewIfNotVisible, showDialog, unlockScroll, waitForAnimationSettled, } from './index.js';
|
package/dist/html/boundary.d.ts
CHANGED
|
@@ -91,6 +91,20 @@ export declare const markSeenForLazyHit: (registry: BoundaryRegistry, trackedIds
|
|
|
91
91
|
* rather than letting events from a destroyed boundary silently
|
|
92
92
|
* misroute. */
|
|
93
93
|
export declare const deregisterBoundaryWrap: (registry: BoundaryRegistry, boundaryId: BoundaryId) => void;
|
|
94
|
+
/** Resolves a message through `boundaryId`'s wrapping chain immediately,
|
|
95
|
+
* applying every `toParentMessage` from innermost to outermost against the
|
|
96
|
+
* wraps present right now, and returns a thunk that dispatches the fully
|
|
97
|
+
* wrapped message via `outerDispatch`. Unlike {@link getOrCreateBoundaryDispatch},
|
|
98
|
+
* which defers the chain lookup to fire time, this snapshots the chain at call
|
|
99
|
+
* time so the resulting thunk survives the boundary being deregistered.
|
|
100
|
+
*
|
|
101
|
+
* Used by `OnUnmount`: its destroy hook fires during the patch that tears the
|
|
102
|
+
* boundary down, after the Submodel's own destroy hook has already removed the
|
|
103
|
+
* wrap, so a fire-time lookup would throw. Resolving eagerly while the chain is
|
|
104
|
+
* still live and dispatching the precomputed root message at destroy time
|
|
105
|
+
* avoids that race. Throws here (at resolve time, boundary alive) if a wrap is
|
|
106
|
+
* somehow already missing, surfacing a real corruption rather than misrouting. */
|
|
107
|
+
export declare const resolveBoundaryDispatchThunk: (registry: BoundaryRegistry, outerDispatch: DispatchSync, boundaryId: BoundaryId, message: unknown) => (() => void);
|
|
94
108
|
export declare const getOrCreateBoundaryDispatch: (registry: BoundaryRegistry, outerDispatch: DispatchSync, boundaryId: BoundaryId) => DispatchSync;
|
|
95
109
|
/** Called at the start of each top-level render. Clears the
|
|
96
110
|
* per-render duplicate-slotId tracking map so siblings inside the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../../src/html/boundary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAA;CAC/C,CAAC,CAAA;AAEF;;;;eAIe;AACf,MAAM,MAAM,UAAU,GAAG,MAAM,CAAA;AAI/B,eAAO,MAAM,aAAa,EAAE,UAAe,CAAA;AAE3C,eAAO,MAAM,eAAe,GAC1B,QAAQ,UAAU,EAClB,SAAS,MAAM,KACd,UAUF,CAAA;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAwCa;AACb,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,UAAU,EAAE,cAAc,CAAC,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAClC,YAAY,EACZ,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC,CAC9B,CAAA;IACD,QAAQ,CAAC,cAAc,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IAChD,QAAQ,CAAC,iBAAiB,EAAE,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAA;IAI1D,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,eAAO,MAAM,sBAAsB,QAAO,gBAMxC,CAAA;AAqBF,eAAO,MAAM,oBAAoB,GAC/B,UAAU,gBAAgB,EAC1B,YAAY,UAAU,EACtB,YAAY,cAAc,KACzB,IA6BF,CAAA;AAED;;;;wCAIwC;AACxC,eAAO,MAAM,iBAAiB,GAC5B,UAAU,gBAAgB,KACzB,GAAG,CAAC,UAAU,EAAE,MAAM,CAIxB,CAAA;AAED;;yCAEyC;AACzC,eAAO,MAAM,eAAe,GAAI,UAAU,gBAAgB,KAAG,IAQ5D,CAAA;AAED;;;;;;;4CAO4C;AAC5C,eAAO,MAAM,kBAAkB,GAC7B,UAAU,gBAAgB,EAC1B,YAAY,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,KAC1C,IAWF,CAAA;AAED;;;;;;;;;gBASgB;AAChB,eAAO,MAAM,sBAAsB,GACjC,UAAU,gBAAgB,EAC1B,YAAY,UAAU,KACrB,IAEF,CAAA;AA8CD,eAAO,MAAM,2BAA2B,GACtC,UAAU,gBAAgB,EAC1B,eAAe,YAAY,EAC3B,YAAY,UAAU,KACrB,YAkBF,CAAA;AAED;;;;;;;;;;iEAUiE;AACjE,eAAO,MAAM,WAAW,GAAI,UAAU,gBAAgB,KAAG,IAGxD,CAAA"}
|
|
1
|
+
{"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../../src/html/boundary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAA;CAC/C,CAAC,CAAA;AAEF;;;;eAIe;AACf,MAAM,MAAM,UAAU,GAAG,MAAM,CAAA;AAI/B,eAAO,MAAM,aAAa,EAAE,UAAe,CAAA;AAE3C,eAAO,MAAM,eAAe,GAC1B,QAAQ,UAAU,EAClB,SAAS,MAAM,KACd,UAUF,CAAA;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAwCa;AACb,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,UAAU,EAAE,cAAc,CAAC,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAClC,YAAY,EACZ,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC,CAC9B,CAAA;IACD,QAAQ,CAAC,cAAc,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IAChD,QAAQ,CAAC,iBAAiB,EAAE,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAA;IAI1D,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,eAAO,MAAM,sBAAsB,QAAO,gBAMxC,CAAA;AAqBF,eAAO,MAAM,oBAAoB,GAC/B,UAAU,gBAAgB,EAC1B,YAAY,UAAU,EACtB,YAAY,cAAc,KACzB,IA6BF,CAAA;AAED;;;;wCAIwC;AACxC,eAAO,MAAM,iBAAiB,GAC5B,UAAU,gBAAgB,KACzB,GAAG,CAAC,UAAU,EAAE,MAAM,CAIxB,CAAA;AAED;;yCAEyC;AACzC,eAAO,MAAM,eAAe,GAAI,UAAU,gBAAgB,KAAG,IAQ5D,CAAA;AAED;;;;;;;4CAO4C;AAC5C,eAAO,MAAM,kBAAkB,GAC7B,UAAU,gBAAgB,EAC1B,YAAY,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,KAC1C,IAWF,CAAA;AAED;;;;;;;;;gBASgB;AAChB,eAAO,MAAM,sBAAsB,GACjC,UAAU,gBAAgB,EAC1B,YAAY,UAAU,KACrB,IAEF,CAAA;AA8CD;;;;;;;;;;;;mFAYmF;AACnF,eAAO,MAAM,4BAA4B,GACvC,UAAU,gBAAgB,EAC1B,eAAe,YAAY,EAC3B,YAAY,UAAU,EACtB,SAAS,OAAO,KACf,CAAC,MAAM,IAAI,CAqBb,CAAA;AAED,eAAO,MAAM,2BAA2B,GACtC,UAAU,gBAAgB,EAC1B,eAAe,YAAY,EAC3B,YAAY,UAAU,KACrB,YAkBF,CAAA;AAED;;;;;;;;;;iEAUiE;AACjE,eAAO,MAAM,WAAW,GAAI,UAAU,gBAAgB,KAAG,IAGxD,CAAA"}
|
package/dist/html/boundary.js
CHANGED
|
@@ -150,6 +150,39 @@ const dispatchAcrossBoundary = (registry, outerDispatch, boundaryId, message) =>
|
|
|
150
150
|
}
|
|
151
151
|
outerDispatch(wrapped);
|
|
152
152
|
};
|
|
153
|
+
/** Resolves a message through `boundaryId`'s wrapping chain immediately,
|
|
154
|
+
* applying every `toParentMessage` from innermost to outermost against the
|
|
155
|
+
* wraps present right now, and returns a thunk that dispatches the fully
|
|
156
|
+
* wrapped message via `outerDispatch`. Unlike {@link getOrCreateBoundaryDispatch},
|
|
157
|
+
* which defers the chain lookup to fire time, this snapshots the chain at call
|
|
158
|
+
* time so the resulting thunk survives the boundary being deregistered.
|
|
159
|
+
*
|
|
160
|
+
* Used by `OnUnmount`: its destroy hook fires during the patch that tears the
|
|
161
|
+
* boundary down, after the Submodel's own destroy hook has already removed the
|
|
162
|
+
* wrap, so a fire-time lookup would throw. Resolving eagerly while the chain is
|
|
163
|
+
* still live and dispatching the precomputed root message at destroy time
|
|
164
|
+
* avoids that race. Throws here (at resolve time, boundary alive) if a wrap is
|
|
165
|
+
* somehow already missing, surfacing a real corruption rather than misrouting. */
|
|
166
|
+
export const resolveBoundaryDispatchThunk = (registry, outerDispatch, boundaryId, message) => {
|
|
167
|
+
if (boundaryId === ROOT_BOUNDARY) {
|
|
168
|
+
return () => outerDispatch(message);
|
|
169
|
+
}
|
|
170
|
+
let wrapped = message;
|
|
171
|
+
const parts = splitBoundary(boundaryId);
|
|
172
|
+
for (let depth = parts.length; depth > 0; depth--) {
|
|
173
|
+
const ancestorBoundary = parts.slice(0, depth).join(BOUNDARY_SEPARATOR);
|
|
174
|
+
const descriptor = registry.wraps.get(ancestorBoundary);
|
|
175
|
+
if (descriptor === undefined) {
|
|
176
|
+
throw new Error(`Foldkit: resolveBoundaryDispatchThunk missing wrap for ancestor ` +
|
|
177
|
+
`"${ancestorBoundary}" of boundary "${boundaryId}" while resolving an ` +
|
|
178
|
+
`OnUnmount message. The Submodel's wrap was absent from the registry ` +
|
|
179
|
+
`at resolve time, which should not happen during a live render.`);
|
|
180
|
+
}
|
|
181
|
+
wrapped = descriptor.toParentMessage(wrapped);
|
|
182
|
+
}
|
|
183
|
+
const rootMessage = wrapped;
|
|
184
|
+
return () => outerDispatch(rootMessage);
|
|
185
|
+
};
|
|
153
186
|
export const getOrCreateBoundaryDispatch = (registry, outerDispatch, boundaryId) => {
|
|
154
187
|
if (boundaryId === ROOT_BOUNDARY) {
|
|
155
188
|
return outerDispatch;
|
|
@@ -7,6 +7,11 @@ declare const BRAND = "__childAttribute";
|
|
|
7
7
|
* produced these; the runtime routes each handler through the
|
|
8
8
|
* originating Submodel's wrap chain at event-fire time.
|
|
9
9
|
*
|
|
10
|
+
* `resolveUnmount` snapshots the boundary's wrapping chain at the time the
|
|
11
|
+
* group was published (child boundary alive) so `OnUnmount` can dispatch a
|
|
12
|
+
* root message from a destroy hook that fires after the boundary has been
|
|
13
|
+
* torn down.
|
|
14
|
+
*
|
|
10
15
|
* Created via {@link childAttributes}. Element constructors accept
|
|
11
16
|
* `ChildAttribute` alongside `Attribute<Message>` in their attribute
|
|
12
17
|
* arrays. */
|
|
@@ -14,6 +19,7 @@ export type ChildAttribute = Readonly<{
|
|
|
14
19
|
readonly [BRAND]: true;
|
|
15
20
|
readonly attribute: unknown;
|
|
16
21
|
readonly dispatch: DispatchSync;
|
|
22
|
+
readonly resolveUnmount: (message: unknown) => () => void;
|
|
17
23
|
}>;
|
|
18
24
|
export declare const isChildAttribute: (value: unknown) => value is ChildAttribute;
|
|
19
25
|
/** Captures the current boundary's dispatcher and wraps each attribute
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"childAttribute.d.ts","sourceRoot":"","sources":["../../src/html/childAttribute.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"childAttribute.d.ts","sourceRoot":"","sources":["../../src/html/childAttribute.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,uBAAuB,CAAA;AAE9B,QAAA,MAAM,KAAK,qBAAqB,CAAA;AAEhC;;;;;;;;;;;;;;cAcc;AACd,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAA;CAC1D,CAAC,CAAA;AAEF,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,cACI,CAAA;AAE/D;;;;;;;;;;;;;;;;;;;;;;oCAsBoC;AACpC,eAAO,MAAM,eAAe,GAAI,SAAS,EACvC,YAAY,aAAa,CAAC,SAAS,CAAC,KACnC,aAAa,CAAC,cAAc,CAS9B,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { requireDispatch } from './runtimeSingleton.js';
|
|
1
|
+
import { requireDispatch, requireUnmountResolver, } from './runtimeSingleton.js';
|
|
2
2
|
const BRAND = '__childAttribute';
|
|
3
3
|
export const isChildAttribute = (value) => typeof value === 'object' && value !== null && BRAND in value;
|
|
4
4
|
/** Captures the current boundary's dispatcher and wraps each attribute
|
|
@@ -26,9 +26,11 @@ export const isChildAttribute = (value) => typeof value === 'object' && value !=
|
|
|
26
26
|
* Submodel's `toParentMessage`. */
|
|
27
27
|
export const childAttributes = (attributes) => {
|
|
28
28
|
const dispatch = requireDispatch();
|
|
29
|
+
const resolveUnmount = requireUnmountResolver();
|
|
29
30
|
return attributes.map(attribute => ({
|
|
30
31
|
[BRAND]: true,
|
|
31
32
|
attribute,
|
|
32
33
|
dispatch,
|
|
34
|
+
resolveUnmount,
|
|
33
35
|
}));
|
|
34
36
|
};
|