foldkit 0.160.0 → 0.161.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/calendar/index.d.ts +1 -1
- package/dist/calendar/index.d.ts.map +1 -1
- package/dist/calendar/index.js +1 -1
- package/dist/calendar/locale.d.ts +219 -10
- package/dist/calendar/locale.d.ts.map +1 -1
- package/dist/calendar/locale.js +166 -82
- package/dist/calendar/public.d.ts +1 -1
- package/dist/calendar/public.d.ts.map +1 -1
- package/dist/calendar/public.js +1 -1
- package/dist/command/index.d.ts +9 -12
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +9 -12
- package/dist/devTools/protocol.d.ts +9 -1
- package/dist/devTools/protocol.d.ts.map +1 -1
- package/dist/devTools/protocol.js +2 -1
- package/dist/devTools/serialize.d.ts.map +1 -1
- package/dist/devTools/serialize.js +1 -0
- package/dist/devTools/store.d.ts +3 -0
- package/dist/devTools/store.d.ts.map +1 -1
- package/dist/devTools/store.js +24 -0
- package/dist/dom/dialogRuntime.d.ts +9 -0
- package/dist/dom/dialogRuntime.d.ts.map +1 -0
- package/dist/dom/dialogRuntime.js +3 -0
- package/dist/dom/dom.d.ts +22 -11
- package/dist/dom/dom.d.ts.map +1 -1
- package/dist/dom/dom.js +196 -18
- package/dist/dom/inert.d.ts +9 -0
- package/dist/dom/inert.d.ts.map +1 -1
- package/dist/dom/inert.js +74 -2
- package/dist/managedResource/managedResource.d.ts +44 -2
- package/dist/managedResource/managedResource.d.ts.map +1 -1
- package/dist/managedResource/managedResource.js +40 -10
- package/dist/runtime/devToolsIntegration.d.ts +8 -3
- package/dist/runtime/devToolsIntegration.d.ts.map +1 -1
- package/dist/runtime/devToolsIntegration.js +20 -3
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +54 -12
- package/dist/subscription/fromEvent.d.ts +103 -28
- package/dist/subscription/fromEvent.d.ts.map +1 -1
- package/dist/subscription/fromEvent.js +48 -27
- package/dist/subscription/keyboardShortcuts.d.ts +110 -0
- package/dist/subscription/keyboardShortcuts.d.ts.map +1 -0
- package/dist/subscription/keyboardShortcuts.js +444 -0
- package/dist/subscription/public.d.ts +3 -1
- package/dist/subscription/public.d.ts.map +1 -1
- package/dist/subscription/public.js +1 -0
- package/dist/subscription/subscription.d.ts +61 -1
- package/dist/subscription/subscription.d.ts.map +1 -1
- package/dist/subscription/subscription.js +51 -8
- package/dist/update/public.d.ts +1 -1
- package/dist/update/public.d.ts.map +1 -1
- package/dist/update/public.js +1 -1
- package/dist/update/update.d.ts +189 -1
- package/dist/update/update.d.ts.map +1 -1
- package/dist/update/update.js +178 -16
- package/package.json +1 -1
package/dist/dom/dom.js
CHANGED
|
@@ -1,11 +1,131 @@
|
|
|
1
|
-
import { Array, Effect, Equal, Function, Match, Number, Option } from 'effect';
|
|
1
|
+
import { Array, Effect, Equal, Function, Match, Number, Option, Result, } from 'effect';
|
|
2
2
|
import { afterCommit, afterPaint } from '../render/render.js';
|
|
3
|
+
import { DialogRuntime } from './dialogRuntime.js';
|
|
3
4
|
import { ElementNotFound } from './error.js';
|
|
5
|
+
import { makeOutsideIsolation } from './inert.js';
|
|
4
6
|
import { unlockScroll } from './scrollLock.js';
|
|
5
7
|
const BASE_DIALOG_Z_INDEX = 2147483600;
|
|
6
8
|
let openDialogCount = 0;
|
|
7
9
|
const dialogHygieneById = new Map();
|
|
8
10
|
let openDialogStack = [];
|
|
11
|
+
let modalIsolation;
|
|
12
|
+
let modalObserver;
|
|
13
|
+
let modalAllowedElements = [];
|
|
14
|
+
let modalAllowedOutsideElements = [];
|
|
15
|
+
let modalAllowedSelectors = [];
|
|
16
|
+
let modalAllowedAncestors = new Set();
|
|
17
|
+
const containsAllowedSelector = (node) => {
|
|
18
|
+
if (!(node instanceof Element || node instanceof DocumentFragment)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return Array.some(modalAllowedSelectors, selector => {
|
|
22
|
+
try {
|
|
23
|
+
return ((node instanceof Element && node.matches(selector)) ||
|
|
24
|
+
node.querySelector(selector) !== null);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const containsCurrentAllowedElement = (node) => Array.some(modalAllowedElements, element => node.contains(element));
|
|
32
|
+
const isRelevantModalMutation = (mutation) => {
|
|
33
|
+
if (mutation.type === 'attributes' &&
|
|
34
|
+
(mutation.attributeName === 'aria-hidden' ||
|
|
35
|
+
mutation.attributeName === 'inert')) {
|
|
36
|
+
return (mutation.target instanceof HTMLElement &&
|
|
37
|
+
modalIsolation?.contains(mutation.target) === true);
|
|
38
|
+
}
|
|
39
|
+
if (mutation.type === 'attributes') {
|
|
40
|
+
return (Array.some(modalAllowedOutsideElements, element => mutation.target.contains(element)) || containsAllowedSelector(mutation.target));
|
|
41
|
+
}
|
|
42
|
+
const changedNodes = [
|
|
43
|
+
...Array.fromIterable(mutation.addedNodes),
|
|
44
|
+
...Array.fromIterable(mutation.removedNodes),
|
|
45
|
+
];
|
|
46
|
+
if (mutation.target instanceof HTMLElement &&
|
|
47
|
+
modalAllowedAncestors.has(mutation.target) &&
|
|
48
|
+
Array.some(changedNodes, node => node instanceof Element)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return Array.some(changedNodes, node => containsCurrentAllowedElement(node) || containsAllowedSelector(node));
|
|
52
|
+
};
|
|
53
|
+
const ancestorsOfAllowedElements = (allowedElements) => {
|
|
54
|
+
const ancestors = new Set();
|
|
55
|
+
for (const allowedElement of allowedElements) {
|
|
56
|
+
let ancestor = allowedElement.parentElement;
|
|
57
|
+
while (ancestor !== null) {
|
|
58
|
+
ancestors.add(ancestor);
|
|
59
|
+
if (ancestor === document.body) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
ancestor = ancestor.parentElement;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return ancestors;
|
|
66
|
+
};
|
|
67
|
+
const recordModalAttributeMutations = (mutations) => {
|
|
68
|
+
for (const mutation of mutations) {
|
|
69
|
+
if (mutation.type === 'attributes' &&
|
|
70
|
+
mutation.target instanceof HTMLElement &&
|
|
71
|
+
(mutation.attributeName === 'aria-hidden' ||
|
|
72
|
+
mutation.attributeName === 'inert')) {
|
|
73
|
+
modalIsolation?.recordAttributeMutation(mutation.target, mutation.attributeName);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const synchronizeModalIsolation = () => {
|
|
78
|
+
if (modalObserver !== undefined) {
|
|
79
|
+
recordModalAttributeMutations(modalObserver.takeRecords());
|
|
80
|
+
}
|
|
81
|
+
modalObserver?.disconnect();
|
|
82
|
+
const maybeTopmostModal = Array.last(Array.filter(openDialogStack, dialog => dialog.isModal));
|
|
83
|
+
if (Option.isNone(maybeTopmostModal)) {
|
|
84
|
+
modalObserver?.disconnect();
|
|
85
|
+
modalObserver = undefined;
|
|
86
|
+
modalIsolation?.dispose();
|
|
87
|
+
modalIsolation = undefined;
|
|
88
|
+
modalAllowedElements = [];
|
|
89
|
+
modalAllowedOutsideElements = [];
|
|
90
|
+
modalAllowedSelectors = [];
|
|
91
|
+
modalAllowedAncestors = new Set();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const { allowedOutsideSelectors } = maybeTopmostModal.value;
|
|
95
|
+
const dialogsAtOrAboveModal = Array.dropWhile(openDialogStack, dialog => dialog !== maybeTopmostModal.value);
|
|
96
|
+
const allowedOutsideElements = Array.filterMap(allowedOutsideSelectors, selector => {
|
|
97
|
+
try {
|
|
98
|
+
const outsideElement = document.querySelector(selector);
|
|
99
|
+
return outsideElement instanceof HTMLElement
|
|
100
|
+
? Result.succeed(outsideElement)
|
|
101
|
+
: Result.failVoid;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return Result.failVoid;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const nextAllowedElements = [
|
|
108
|
+
...Array.map(dialogsAtOrAboveModal, dialog => dialog.element),
|
|
109
|
+
...allowedOutsideElements,
|
|
110
|
+
];
|
|
111
|
+
modalIsolation ??= makeOutsideIsolation();
|
|
112
|
+
modalIsolation.update(nextAllowedElements);
|
|
113
|
+
modalAllowedElements = nextAllowedElements;
|
|
114
|
+
modalAllowedOutsideElements = allowedOutsideElements;
|
|
115
|
+
modalAllowedSelectors = allowedOutsideSelectors;
|
|
116
|
+
modalAllowedAncestors = ancestorsOfAllowedElements(nextAllowedElements);
|
|
117
|
+
modalObserver ??= new MutationObserver(mutations => {
|
|
118
|
+
if (Array.some(mutations, isRelevantModalMutation)) {
|
|
119
|
+
recordModalAttributeMutations(mutations);
|
|
120
|
+
synchronizeModalIsolation();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
modalObserver.observe(document.body, {
|
|
124
|
+
childList: true,
|
|
125
|
+
subtree: true,
|
|
126
|
+
attributes: true,
|
|
127
|
+
});
|
|
128
|
+
};
|
|
9
129
|
const FOCUSABLE_SELECTOR = Array.join([
|
|
10
130
|
'a[href]:not([tabindex="-1"])',
|
|
11
131
|
'button:not([disabled]):not([tabindex="-1"])',
|
|
@@ -14,6 +134,8 @@ const FOCUSABLE_SELECTOR = Array.join([
|
|
|
14
134
|
'textarea:not([disabled]):not([tabindex="-1"])',
|
|
15
135
|
'[tabindex]:not([tabindex="-1"])',
|
|
16
136
|
], ', ');
|
|
137
|
+
const isVisibleAndInteractiveElement = (element) => element.closest('[hidden], [inert]') === null &&
|
|
138
|
+
element.checkVisibility({ visibilityProperty: true });
|
|
17
139
|
const isFocusableElement = (element) => {
|
|
18
140
|
if (!(element instanceof HTMLElement)) {
|
|
19
141
|
return false;
|
|
@@ -27,12 +149,18 @@ const isFocusableElement = (element) => {
|
|
|
27
149
|
if (element.tabIndex < 0 && !element.hasAttribute('tabindex')) {
|
|
28
150
|
return false;
|
|
29
151
|
}
|
|
30
|
-
|
|
152
|
+
return isVisibleAndInteractiveElement(element);
|
|
153
|
+
};
|
|
154
|
+
const focusableElementsWithin = (root) => Array.filter(Array.fromIterable(root.querySelectorAll(FOCUSABLE_SELECTOR)), isFocusableElement);
|
|
155
|
+
const isValidReturnFocus = (element) => {
|
|
156
|
+
if (!document.contains(element)) {
|
|
31
157
|
return false;
|
|
32
158
|
}
|
|
33
|
-
|
|
159
|
+
if (element instanceof HTMLDialogElement) {
|
|
160
|
+
return element.open && isVisibleAndInteractiveElement(element);
|
|
161
|
+
}
|
|
162
|
+
return isFocusableElement(element);
|
|
34
163
|
};
|
|
35
|
-
const focusableElementsWithin = (root) => Array.filter(Array.fromIterable(root.querySelectorAll(FOCUSABLE_SELECTOR)), isFocusableElement);
|
|
36
164
|
const queryHTMLElement = (selector) => Effect.suspend(() => {
|
|
37
165
|
const element = document.querySelector(selector);
|
|
38
166
|
return element instanceof HTMLElement
|
|
@@ -101,7 +229,11 @@ export const focus = (selector, options) => Effect.gen(function* () {
|
|
|
101
229
|
* a `CustomEvent` named `cancel`, distinguishing it from native `cancel` events
|
|
102
230
|
* while preserving the dialog event contract. Uses `show()` instead of
|
|
103
231
|
* `showModal()` so that DevTools (and any other high-z-index overlay) remains
|
|
104
|
-
* interactive.
|
|
232
|
+
* interactive. Pass `isModal: true` to make the background inert and hide it
|
|
233
|
+
* from assistive technology. `allowedOutsideSelectors` keeps separate developer
|
|
234
|
+
* overlays available while modal isolation is active. Stacked modal dialogs
|
|
235
|
+
* isolate against the topmost one, and closing it restores the dialog beneath.
|
|
236
|
+
* The Dialog component provides its own backdrop, scroll locking,
|
|
105
237
|
* and transitions. Fails with `ElementNotFound` if the selector does not match
|
|
106
238
|
* an `HTMLDialogElement`.
|
|
107
239
|
*
|
|
@@ -110,12 +242,16 @@ export const focus = (selector, options) => Effect.gen(function* () {
|
|
|
110
242
|
* falls back to the first focusable descendant and then to the dialog itself.
|
|
111
243
|
*
|
|
112
244
|
* Records the element that had focus when the dialog opened so `closeDialog`
|
|
113
|
-
* can return focus there, the way `showModal()` would natively.
|
|
245
|
+
* can return focus there, the way `showModal()` would natively. Resolves to
|
|
246
|
+
* `true` when it installs the dialog resources, or `false` when that id already
|
|
247
|
+
* holds them. The latter makes concurrent lifecycle recovery and application
|
|
248
|
+
* Commands safe without duplicating focus traps or stack entries.
|
|
114
249
|
*
|
|
115
250
|
* @example
|
|
116
251
|
* ```typescript
|
|
117
252
|
* Dom.showDialog('#my-dialog')
|
|
118
253
|
* Dom.showDialog('#my-dialog', { focusSelector: '#search-input' })
|
|
254
|
+
* Dom.showDialog('#my-dialog', { isModal: true })
|
|
119
255
|
* ```
|
|
120
256
|
*/
|
|
121
257
|
export const showDialog = (selector, options) => Effect.gen(function* () {
|
|
@@ -125,17 +261,28 @@ export const showDialog = (selector, options) => Effect.gen(function* () {
|
|
|
125
261
|
return yield* Effect.fail(new ElementNotFound({ selector }));
|
|
126
262
|
}
|
|
127
263
|
const { id } = element;
|
|
264
|
+
const maybeDialogRuntime = yield* Effect.serviceOption(DialogRuntime);
|
|
265
|
+
const existingHygiene = dialogHygieneById.get(id);
|
|
266
|
+
if (existingHygiene !== undefined) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
128
269
|
element.style.position = 'fixed';
|
|
129
270
|
element.style.inset = '0';
|
|
130
|
-
openDialogCount++;
|
|
131
|
-
element.style.zIndex = String(BASE_DIALOG_Z_INDEX + openDialogCount);
|
|
132
271
|
const previouslyFocused = document.activeElement;
|
|
133
272
|
const returnFocus = previouslyFocused instanceof HTMLElement &&
|
|
134
273
|
previouslyFocused !== document.body
|
|
135
274
|
? previouslyFocused
|
|
136
275
|
: undefined;
|
|
137
276
|
element.show();
|
|
138
|
-
|
|
277
|
+
openDialogCount++;
|
|
278
|
+
element.style.zIndex = String(BASE_DIALOG_Z_INDEX + openDialogCount);
|
|
279
|
+
openDialogStack = Array.append(Array.filter(openDialogStack, dialog => dialog.element !== element), {
|
|
280
|
+
id,
|
|
281
|
+
element,
|
|
282
|
+
isModal: options?.isModal ?? false,
|
|
283
|
+
allowedOutsideSelectors: options?.allowedOutsideSelectors ?? [],
|
|
284
|
+
});
|
|
285
|
+
synchronizeModalIsolation();
|
|
139
286
|
const handleKeydown = (event) => {
|
|
140
287
|
if (!element.open) {
|
|
141
288
|
return;
|
|
@@ -157,10 +304,17 @@ export const showDialog = (selector, options) => Effect.gen(function* () {
|
|
|
157
304
|
document.addEventListener('keydown', handleKeydown);
|
|
158
305
|
dialogHygieneById.set(id, {
|
|
159
306
|
removeKeydownListener: () => document.removeEventListener('keydown', handleKeydown),
|
|
307
|
+
unregisterOwner: Option.isSome(maybeDialogRuntime)
|
|
308
|
+
? () => maybeDialogRuntime.value.unregister(id)
|
|
309
|
+
: undefined,
|
|
160
310
|
returnFocus,
|
|
161
311
|
});
|
|
312
|
+
if (Option.isSome(maybeDialogRuntime)) {
|
|
313
|
+
maybeDialogRuntime.value.register(id);
|
|
314
|
+
}
|
|
162
315
|
const focusTarget = findDialogFocusTarget(element, options?.focusSelector);
|
|
163
316
|
focusTarget.focus();
|
|
317
|
+
return true;
|
|
164
318
|
});
|
|
165
319
|
const findDialogFocusTarget = (dialog, focusSelector) => {
|
|
166
320
|
if (focusSelector !== undefined) {
|
|
@@ -196,9 +350,10 @@ const trapFocusWithinDialog = (event, dialog) => {
|
|
|
196
350
|
};
|
|
197
351
|
/**
|
|
198
352
|
* Closes a dialog element using `.close()`.
|
|
199
|
-
* Cleans up the keyboard handlers installed by `showDialog
|
|
200
|
-
*
|
|
201
|
-
* dialog beneath it when closing
|
|
353
|
+
* Cleans up the keyboard handlers installed by `showDialog`, restores modal
|
|
354
|
+
* background isolation, and then returns focus to the element that was focused
|
|
355
|
+
* before the dialog opened (the trigger, or the dialog beneath it when closing
|
|
356
|
+
* a stacked dialog).
|
|
202
357
|
* Resolves to `true` when it released the keyboard handlers, the return
|
|
203
358
|
* focus, and the stack entry.
|
|
204
359
|
* Resolves to `false` when the dialog held none, for example when the close
|
|
@@ -224,23 +379,46 @@ const releaseDialogHygieneById = (id) => {
|
|
|
224
379
|
if (hygiene === undefined) {
|
|
225
380
|
return false;
|
|
226
381
|
}
|
|
382
|
+
const maybeReleasedDialog = Array.findFirst(openDialogStack, dialog => dialog.id === id);
|
|
383
|
+
const isTopmost = Option.exists(Array.last(openDialogStack), dialog => dialog.id === id);
|
|
384
|
+
if (Option.isSome(maybeReleasedDialog)) {
|
|
385
|
+
for (const openDialog of openDialogStack) {
|
|
386
|
+
if (openDialog.id === id) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const openHygiene = dialogHygieneById.get(openDialog.id);
|
|
390
|
+
const openReturnFocus = openHygiene?.returnFocus;
|
|
391
|
+
if (openHygiene !== undefined &&
|
|
392
|
+
openReturnFocus !== undefined &&
|
|
393
|
+
maybeReleasedDialog.value.element.contains(openReturnFocus)) {
|
|
394
|
+
dialogHygieneById.set(openDialog.id, {
|
|
395
|
+
...openHygiene,
|
|
396
|
+
returnFocus: hygiene.returnFocus,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
227
401
|
openDialogStack = Array.filter(openDialogStack, dialog => dialog.id !== id);
|
|
402
|
+
synchronizeModalIsolation();
|
|
228
403
|
openDialogCount = Math.max(0, Number.decrement(openDialogCount));
|
|
229
404
|
hygiene.removeKeydownListener();
|
|
230
405
|
dialogHygieneById.delete(id);
|
|
406
|
+
hygiene.unregisterOwner?.();
|
|
231
407
|
const { returnFocus } = hygiene;
|
|
232
|
-
if (
|
|
408
|
+
if (isTopmost &&
|
|
409
|
+
returnFocus !== undefined &&
|
|
410
|
+
isValidReturnFocus(returnFocus)) {
|
|
233
411
|
returnFocus.focus();
|
|
234
412
|
}
|
|
235
413
|
return true;
|
|
236
414
|
};
|
|
237
415
|
/**
|
|
238
416
|
* Releases the framework hygiene a dialog holds while open: the focus-trap
|
|
239
|
-
* keyboard handler,
|
|
240
|
-
* z-index counter, and one page scroll lock.
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
417
|
+
* keyboard handler, modal background isolation, the recorded return focus,
|
|
418
|
+
* the dialog stack entry, the z-index counter, and one page scroll lock.
|
|
419
|
+
* Use this when the element is removed without a close Message, such as
|
|
420
|
+
* navigation away from a route-keyed subtree. The runtime also calls it
|
|
421
|
+
* directly on disposal. The normal close path already releases these.
|
|
244
422
|
* That path is `closeDialog` first, then the Dialog component's scroll unlock
|
|
245
423
|
* when `closeDialog` reports a release. This function is the cleanup for the
|
|
246
424
|
* case where no close Message ever reaches `update`.
|
package/dist/dom/inert.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { Effect } from 'effect';
|
|
2
|
+
type InertAttribute = 'aria-hidden' | 'inert';
|
|
3
|
+
export declare const makeOutsideIsolation: () => Readonly<{
|
|
4
|
+
update: (allowedElements: ReadonlyArray<HTMLElement>) => void;
|
|
5
|
+
contains: (element: HTMLElement) => boolean;
|
|
6
|
+
recordAttributeMutation: (element: HTMLElement, attributeName: InertAttribute) => void;
|
|
7
|
+
dispose: () => void;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const isolateOutsideElements: (allowedElements: ReadonlyArray<HTMLElement>) => (() => void);
|
|
2
10
|
/**
|
|
3
11
|
* Marks all DOM elements outside the given selectors as `inert` and
|
|
4
12
|
* `aria-hidden="true"`. Walks each allowed element up to `document.body`,
|
|
@@ -23,4 +31,5 @@ export declare const inertOthers: (id: string, allowedSelectors: ReadonlyArray<s
|
|
|
23
31
|
* ```
|
|
24
32
|
*/
|
|
25
33
|
export declare const restoreInert: (id: string) => Effect.Effect<void>;
|
|
34
|
+
export {};
|
|
26
35
|
//# sourceMappingURL=inert.d.ts.map
|
package/dist/dom/inert.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inert.d.ts","sourceRoot":"","sources":["../../src/dom/inert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAA2C,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"inert.d.ts","sourceRoot":"","sources":["../../src/dom/inert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAA2C,MAAM,QAAQ,CAAA;AAc/E,KAAK,cAAc,GAAG,aAAa,GAAG,OAAO,CAAA;AAoH7C,eAAO,MAAM,oBAAoB,QAAO,QAAQ,CAAC;IAC/C,MAAM,EAAE,CAAC,eAAe,EAAE,aAAa,CAAC,WAAW,CAAC,KAAK,IAAI,CAAA;IAC7D,QAAQ,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAA;IAC3C,uBAAuB,EAAE,CACvB,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,cAAc,KAC1B,IAAI,CAAA;IACT,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAyCA,CAAA;AAED,eAAO,MAAM,sBAAsB,oBAChB,aAAa,CAAC,WAAW,CAAC,KAC1C,CAAC,MAAM,IAAI,CAKb,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,OAClB,MAAM,oBACQ,aAAa,CAAC,MAAM,CAAC,KACtC,MAAM,CAAC,MAAM,CAAC,IAAI,CAiBjB,CAAA;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,OAAQ,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAUxD,CAAA"}
|
package/dist/dom/inert.js
CHANGED
|
@@ -39,6 +39,24 @@ const markNotInert = (element) => {
|
|
|
39
39
|
inertState.counts.set(element, Number.decrement(count));
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
+
const recordCurrentAttribute = (element, attributeName) => {
|
|
43
|
+
const original = inertState.originals.get(element);
|
|
44
|
+
if (original === undefined) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (attributeName === 'aria-hidden') {
|
|
48
|
+
inertState.originals.set(element, {
|
|
49
|
+
...original,
|
|
50
|
+
ariaHidden: element.getAttribute('aria-hidden'),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
inertState.originals.set(element, {
|
|
55
|
+
...original,
|
|
56
|
+
inert: element.inert,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
42
60
|
const resolveElements = (selectors) => Array.filterMap(selectors, selector => Result.liftPredicate(document.querySelector(selector), (element) => element instanceof HTMLElement, () => undefined));
|
|
43
61
|
const ancestorsUpToBody = (element) => Array.unfold(element.parentElement, current => Predicate.isNotNull(current)
|
|
44
62
|
? Option.some([
|
|
@@ -50,6 +68,61 @@ const inertableSiblings = (parent, allowedElements) => pipe(parent.children, Arr
|
|
|
50
68
|
!Array.some(allowedElements, allowed => child.contains(allowed))
|
|
51
69
|
? Result.succeed(child)
|
|
52
70
|
: Result.failVoid));
|
|
71
|
+
const outsideCandidates = (allowedElements) => {
|
|
72
|
+
const candidates = new Set();
|
|
73
|
+
for (const allowedElement of allowedElements) {
|
|
74
|
+
for (const ancestor of ancestorsUpToBody(allowedElement)) {
|
|
75
|
+
for (const sibling of inertableSiblings(ancestor, allowedElements)) {
|
|
76
|
+
candidates.add(sibling);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return candidates;
|
|
81
|
+
};
|
|
82
|
+
export const makeOutsideIsolation = () => {
|
|
83
|
+
const cleanups = new Map();
|
|
84
|
+
return {
|
|
85
|
+
update: allowedElements => {
|
|
86
|
+
const candidates = outsideCandidates(allowedElements);
|
|
87
|
+
for (const [element, cleanup] of cleanups) {
|
|
88
|
+
if (!candidates.has(element)) {
|
|
89
|
+
cleanup();
|
|
90
|
+
cleanups.delete(element);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const element of candidates) {
|
|
94
|
+
if (!cleanups.has(element)) {
|
|
95
|
+
cleanups.set(element, markInert(element));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
if (element.getAttribute('aria-hidden') !== 'true') {
|
|
99
|
+
element.setAttribute('aria-hidden', 'true');
|
|
100
|
+
}
|
|
101
|
+
if (!element.inert) {
|
|
102
|
+
element.inert = true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
contains: element => cleanups.has(element),
|
|
108
|
+
recordAttributeMutation: (element, attributeName) => {
|
|
109
|
+
if (cleanups.has(element)) {
|
|
110
|
+
recordCurrentAttribute(element, attributeName);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
dispose: () => {
|
|
114
|
+
for (const cleanup of cleanups.values()) {
|
|
115
|
+
cleanup();
|
|
116
|
+
}
|
|
117
|
+
cleanups.clear();
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
export const isolateOutsideElements = (allowedElements) => {
|
|
122
|
+
const isolation = makeOutsideIsolation();
|
|
123
|
+
isolation.update(allowedElements);
|
|
124
|
+
return isolation.dispose;
|
|
125
|
+
};
|
|
53
126
|
/**
|
|
54
127
|
* Marks all DOM elements outside the given selectors as `inert` and
|
|
55
128
|
* `aria-hidden="true"`. Walks each allowed element up to `document.body`,
|
|
@@ -73,8 +146,7 @@ export const inertOthers = (id, allowedSelectors) => Effect.gen(function* () {
|
|
|
73
146
|
return;
|
|
74
147
|
}
|
|
75
148
|
const allowedElements = resolveElements(allowedSelectors);
|
|
76
|
-
|
|
77
|
-
inertState.cleanups.set(id, cleanupFunctions);
|
|
149
|
+
inertState.cleanups.set(id, [isolateOutsideElements(allowedElements)]);
|
|
78
150
|
});
|
|
79
151
|
/**
|
|
80
152
|
* Restores all elements previously marked inert by `inertOthers` for the
|
|
@@ -224,12 +224,54 @@ export declare const lift: <Resources extends Record<string, Entry<any, any, Opt
|
|
|
224
224
|
readonly toChildModel: (parentModel: ParentModel) => Option.Option<ChildModelOf<Resources>>;
|
|
225
225
|
readonly toParentMessage: (message: ChildMessageOf<Resources>) => ParentMessage;
|
|
226
226
|
}) => { readonly [Key in keyof Resources]: Resources[Key] extends Entry<any, any, infer Requirements, infer Value, infer Service, infer OnAcquired extends (...args: ReadonlyArray<any>) => any> ? Entry<ParentModel, ParentMessage, Requirements, Value, Service, (...args: Parameters<OnAcquired>) => ParentMessage> : never; };
|
|
227
|
-
type
|
|
227
|
+
type RuntimeKey<Key> = Key extends string ? Key : Key extends number ? `${Key}` : never;
|
|
228
|
+
type RuntimeEntries<RecordType> = {
|
|
229
|
+
readonly [Key in keyof RecordType as RuntimeKey<Key>]: RecordType[Key];
|
|
230
|
+
};
|
|
231
|
+
type MergeRecords<Records extends ReadonlyArray<unknown>> = Records extends readonly [infer Head, ...infer Rest] ? RuntimeEntries<Head> & (Rest extends ReadonlyArray<unknown> ? MergeRecords<Rest> : {}) : {};
|
|
232
|
+
type AnyResources = Readonly<Record<string, Entry<any, any, any, any, any>>>;
|
|
233
|
+
type AnyResourcesList = readonly [AnyResources, ...ReadonlyArray<AnyResources>];
|
|
234
|
+
type EntriesOfRecord<ResourcesRecord> = ResourcesRecord extends unknown ? ResourcesRecord[keyof ResourcesRecord] : never;
|
|
235
|
+
type ModelOfEntry<AnyEntry> = [AnyEntry] extends [
|
|
236
|
+
Entry<infer Model, any, any, any, any>
|
|
237
|
+
] ? unknown extends Model ? never : Model : never;
|
|
238
|
+
type MessageOfEntry<AnyEntry> = AnyEntry extends Entry<any, infer Message, any, any, any> ? Message : never;
|
|
239
|
+
type MessageOf<Records extends AnyResourcesList> = MessageOfEntry<EntriesOfRecord<Records[number]>>;
|
|
240
|
+
type ReferenceModel<Records extends ReadonlyArray<AnyResources>> = Records extends readonly [
|
|
241
|
+
infer Head extends AnyResources,
|
|
242
|
+
...infer Rest extends ReadonlyArray<AnyResources>
|
|
243
|
+
] ? [ModelOfEntry<EntriesOfRecord<Head>>] extends [never] ? ReferenceModel<Rest> : ModelOfEntry<EntriesOfRecord<Head>> : never;
|
|
244
|
+
type CompatibleResources<Records extends AnyResourcesList> = {
|
|
245
|
+
readonly [Index in keyof Records]: Records[Index] & Readonly<Record<string, Entry<ReferenceModel<Records>, MessageOf<Records>, any, any, any>>>;
|
|
246
|
+
};
|
|
228
247
|
/**
|
|
229
248
|
* Combines multiple Managed Resources records into one. Throws on duplicate
|
|
230
249
|
* keys so a misconfigured aggregate fails loudly at startup rather than
|
|
231
250
|
* silently overriding.
|
|
251
|
+
*
|
|
252
|
+
* Pass the records directly and the Model and Message are read off them. The
|
|
253
|
+
* Model of the first record is the one every later record is checked against,
|
|
254
|
+
* so a record from another Model universe fails at its own argument position.
|
|
255
|
+
* Message widens to the union across all records.
|
|
256
|
+
*
|
|
257
|
+
* The result keeps each record's keys, each entry's requirements schema and
|
|
258
|
+
* resource service, and the arity of each `onAcquired` handler. Read the
|
|
259
|
+
* service union off the result with {@link ServicesOf}.
|
|
260
|
+
*
|
|
261
|
+
* The curried form remains available for a record that has to be typed before
|
|
262
|
+
* its entries exist, such as a value annotated at a module boundary.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* const managedResources = ManagedResource.aggregate(
|
|
267
|
+
* playgroundManagedResources,
|
|
268
|
+
* notePlayerManagedResources,
|
|
269
|
+
* )
|
|
270
|
+
* ```
|
|
232
271
|
*/
|
|
233
|
-
export declare const aggregate:
|
|
272
|
+
export declare const aggregate: {
|
|
273
|
+
<Model, Message>(): <Records extends ReadonlyArray<Record<string, Entry<Model, Message, any, any, any>>>>(...records: Records) => MergeRecords<Records>;
|
|
274
|
+
<Records extends AnyResourcesList>(...records: CompatibleResources<Records>): MergeRecords<Records>;
|
|
275
|
+
};
|
|
234
276
|
export {};
|
|
235
277
|
//# sourceMappingURL=managedResource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managedResource.d.ts","sourceRoot":"","sources":["../../src/managedResource/managedResource.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"managedResource.d.ts","sourceRoot":"","sources":["../../src/managedResource/managedResource.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Data, Effect, Option, Record, Ref, } from 'effect';
|
|
1
|
+
import { Array, Context, Data, Effect, Option, Record, Ref, } from 'effect';
|
|
2
2
|
/** Typed error raised when a command accesses a managed resource that is not currently acquired. */
|
|
3
3
|
export class ResourceNotAvailable extends Data.TaggedError('ResourceNotAvailable') {
|
|
4
4
|
}
|
|
@@ -144,22 +144,52 @@ Record.map(resources, resource => ({
|
|
|
144
144
|
onReleased: () => config.toParentMessage(resource.onReleased()),
|
|
145
145
|
onAcquireError: (error) => config.toParentMessage(resource.onAcquireError(error)),
|
|
146
146
|
}));
|
|
147
|
-
|
|
148
|
-
* Combines multiple Managed Resources records into one. Throws on duplicate
|
|
149
|
-
* keys so a misconfigured aggregate fails loudly at startup rather than
|
|
150
|
-
* silently overriding.
|
|
151
|
-
*/
|
|
152
|
-
export const aggregate = () => (...records) => {
|
|
147
|
+
const mergeResources = (records) => {
|
|
153
148
|
const result = {};
|
|
154
149
|
for (const record of records) {
|
|
155
150
|
for (const key of Object.keys(record)) {
|
|
156
151
|
if (Object.hasOwn(result, key)) {
|
|
157
152
|
throw new Error(`ManagedResource.aggregate: duplicate key "${key}" across records`);
|
|
158
153
|
}
|
|
159
|
-
|
|
160
|
-
|
|
154
|
+
Object.defineProperty(result, key, {
|
|
155
|
+
configurable: true,
|
|
156
|
+
enumerable: true,
|
|
157
|
+
value: record[key],
|
|
158
|
+
writable: true,
|
|
159
|
+
});
|
|
161
160
|
}
|
|
162
161
|
}
|
|
163
|
-
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
164
162
|
return result;
|
|
165
163
|
};
|
|
164
|
+
/**
|
|
165
|
+
* Combines multiple Managed Resources records into one. Throws on duplicate
|
|
166
|
+
* keys so a misconfigured aggregate fails loudly at startup rather than
|
|
167
|
+
* silently overriding.
|
|
168
|
+
*
|
|
169
|
+
* Pass the records directly and the Model and Message are read off them. The
|
|
170
|
+
* Model of the first record is the one every later record is checked against,
|
|
171
|
+
* so a record from another Model universe fails at its own argument position.
|
|
172
|
+
* Message widens to the union across all records.
|
|
173
|
+
*
|
|
174
|
+
* The result keeps each record's keys, each entry's requirements schema and
|
|
175
|
+
* resource service, and the arity of each `onAcquired` handler. Read the
|
|
176
|
+
* service union off the result with {@link ServicesOf}.
|
|
177
|
+
*
|
|
178
|
+
* The curried form remains available for a record that has to be typed before
|
|
179
|
+
* its entries exist, such as a value annotated at a module boundary.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* const managedResources = ManagedResource.aggregate(
|
|
184
|
+
* playgroundManagedResources,
|
|
185
|
+
* notePlayerManagedResources,
|
|
186
|
+
* )
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export const aggregate = ((...records) => {
|
|
190
|
+
return Array.match(records, {
|
|
191
|
+
onEmpty: () => (...curriedRecords) => mergeResources(curriedRecords),
|
|
192
|
+
onNonEmpty: mergeResources,
|
|
193
|
+
});
|
|
194
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
195
|
+
});
|
|
@@ -7,10 +7,13 @@ export type MountEvents = Readonly<{
|
|
|
7
7
|
starts: ReadonlyArray<MountRecord>;
|
|
8
8
|
ends: ReadonlyArray<MountRecord>;
|
|
9
9
|
}>;
|
|
10
|
-
/**
|
|
10
|
+
/** A Command invocation and its runtime-local identity for DevTools history. */
|
|
11
11
|
export type RecordableCommand = Readonly<{
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
id: number;
|
|
13
|
+
command: Readonly<{
|
|
14
|
+
name: string;
|
|
15
|
+
args?: Record<string, unknown>;
|
|
16
|
+
}>;
|
|
14
17
|
}>;
|
|
15
18
|
/** The replay bridge the render side hands the store when it is installed. */
|
|
16
19
|
export type DevToolsRenderBridge = Readonly<{
|
|
@@ -27,6 +30,7 @@ export type DevToolsRenderBridge = Readonly<{
|
|
|
27
30
|
* helpers no-op until the store exists.
|
|
28
31
|
*/
|
|
29
32
|
export type DevToolsIntegration<Model, Message> = Readonly<{
|
|
33
|
+
isRecordingCommands: boolean;
|
|
30
34
|
mountTracker: typeof MountTracker.Service;
|
|
31
35
|
mountRuntime: typeof MountRuntime.Service;
|
|
32
36
|
drainMountEvents: () => MountEvents;
|
|
@@ -37,6 +41,7 @@ export type DevToolsIntegration<Model, Message> = Readonly<{
|
|
|
37
41
|
resumeDevTools: Effect.Effect<void>;
|
|
38
42
|
recordInit: (initModel: Model, initCommands: ReadonlyArray<RecordableCommand>) => Effect.Effect<void>;
|
|
39
43
|
recordMessage: (message: Message, currentModel: Model, nextModel: Model, commands: ReadonlyArray<RecordableCommand>) => void;
|
|
44
|
+
recordCommandResult: (id: number, message: Message) => void;
|
|
40
45
|
attachRenderedMounts: () => void;
|
|
41
46
|
}>;
|
|
42
47
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devToolsIntegration.d.ts","sourceRoot":"","sources":["../../src/runtime/devToolsIntegration.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAIN,KAAK,KAAK,EAGX,MAAM,QAAQ,CAAA;AAEf,OAAO,EAGL,KAAK,WAAW,EAEjB,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"devToolsIntegration.d.ts","sourceRoot":"","sources":["../../src/runtime/devToolsIntegration.ts"],"names":[],"mappings":"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"}
|