foldkit 0.82.8 → 0.82.9
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/task/dom.d.ts +11 -1
- package/dist/task/dom.d.ts.map +1 -1
- package/dist/task/dom.js +44 -32
- package/package.json +1 -1
package/dist/task/dom.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { Effect } from 'effect';
|
|
2
2
|
import { ElementNotFound } from './error.js';
|
|
3
3
|
/**
|
|
4
|
-
* Focuses an element matching the given selector
|
|
4
|
+
* Focuses an element matching the given selector after the next render has
|
|
5
|
+
* committed.
|
|
6
|
+
*
|
|
7
|
+
* For focus that should happen because an element just appeared (an input
|
|
8
|
+
* mounting, a dialog opening), prefer `OnMount` with a `Mount.define`'d
|
|
9
|
+
* action — focus is then a consequence of the element's lifecycle and runs
|
|
10
|
+
* synchronously inside the snabbdom insert hook with no race window.
|
|
11
|
+
* Reserve `Task.focus` for the post-Message case (returning focus to a
|
|
12
|
+
* trigger button after a popover closes, refocusing on blur, keyboard
|
|
13
|
+
* navigation across a stable layout).
|
|
14
|
+
*
|
|
5
15
|
* Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
|
|
6
16
|
*
|
|
7
17
|
* @example
|
package/dist/task/dom.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/task/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAEf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/task/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAEf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAsC5C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,KAAK,GAAI,UAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKxE,CAAA;AAEJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAgDlC,CAAA;AAuBJ;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAclC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ,0EAA0E;AAC1E,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/task/dom.js
CHANGED
|
@@ -11,8 +11,32 @@ const FOCUSABLE_SELECTOR = Array.join([
|
|
|
11
11
|
'textarea:not([disabled]):not([tabindex="-1"])',
|
|
12
12
|
'[tabindex]:not([tabindex="-1"])',
|
|
13
13
|
], ', ');
|
|
14
|
+
// NOTE: DOM tasks await one rAF before walking the tree. The runtime defers
|
|
15
|
+
// renders to the next animation frame and forks Commands on the microtask
|
|
16
|
+
// queue, so without this wait a Task that runs immediately after a dirtying
|
|
17
|
+
// Message would query the DOM before the matching VDOM patch has committed.
|
|
18
|
+
const awaitNextFrame = Effect.callback(resume => {
|
|
19
|
+
const handle = requestAnimationFrame(() => resume(Effect.void));
|
|
20
|
+
return Effect.sync(() => cancelAnimationFrame(handle));
|
|
21
|
+
});
|
|
22
|
+
const queryHTMLElement = (selector) => Effect.suspend(() => {
|
|
23
|
+
const element = document.querySelector(selector);
|
|
24
|
+
return element instanceof HTMLElement
|
|
25
|
+
? Effect.succeed(element)
|
|
26
|
+
: Effect.fail(new ElementNotFound({ selector }));
|
|
27
|
+
});
|
|
14
28
|
/**
|
|
15
|
-
* Focuses an element matching the given selector
|
|
29
|
+
* Focuses an element matching the given selector after the next render has
|
|
30
|
+
* committed.
|
|
31
|
+
*
|
|
32
|
+
* For focus that should happen because an element just appeared (an input
|
|
33
|
+
* mounting, a dialog opening), prefer `OnMount` with a `Mount.define`'d
|
|
34
|
+
* action — focus is then a consequence of the element's lifecycle and runs
|
|
35
|
+
* synchronously inside the snabbdom insert hook with no race window.
|
|
36
|
+
* Reserve `Task.focus` for the post-Message case (returning focus to a
|
|
37
|
+
* trigger button after a popover closes, refocusing on blur, keyboard
|
|
38
|
+
* navigation across a stable layout).
|
|
39
|
+
*
|
|
16
40
|
* Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
|
|
17
41
|
*
|
|
18
42
|
* @example
|
|
@@ -20,13 +44,10 @@ const FOCUSABLE_SELECTOR = Array.join([
|
|
|
20
44
|
* Task.focus('#email-input').pipe(Effect.ignore, Effect.as(CompletedFocusInput()))
|
|
21
45
|
* ```
|
|
22
46
|
*/
|
|
23
|
-
export const focus = (selector) => Effect.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return Effect.void;
|
|
28
|
-
}
|
|
29
|
-
return Effect.fail(new ElementNotFound({ selector }));
|
|
47
|
+
export const focus = (selector) => Effect.gen(function* () {
|
|
48
|
+
yield* awaitNextFrame;
|
|
49
|
+
const element = yield* queryHTMLElement(selector);
|
|
50
|
+
element.focus();
|
|
30
51
|
});
|
|
31
52
|
/**
|
|
32
53
|
* Opens a dialog element using `show()` with high z-index, focus trapping,
|
|
@@ -43,10 +64,11 @@ export const focus = (selector) => Effect.suspend(() => {
|
|
|
43
64
|
* Task.showModal('#my-dialog', { focusSelector: '#search-input' }).pipe(Effect.ignore, Effect.as(CompletedShowDialog()))
|
|
44
65
|
* ```
|
|
45
66
|
*/
|
|
46
|
-
export const showModal = (selector, options) => Effect.
|
|
67
|
+
export const showModal = (selector, options) => Effect.gen(function* () {
|
|
68
|
+
yield* awaitNextFrame;
|
|
47
69
|
const element = document.querySelector(selector);
|
|
48
70
|
if (!(element instanceof HTMLDialogElement)) {
|
|
49
|
-
return Effect.fail(new ElementNotFound({ selector }));
|
|
71
|
+
return yield* Effect.fail(new ElementNotFound({ selector }));
|
|
50
72
|
}
|
|
51
73
|
element.style.position = 'fixed';
|
|
52
74
|
element.style.inset = '0';
|
|
@@ -75,7 +97,6 @@ export const showModal = (selector, options) => Effect.suspend(() => {
|
|
|
75
97
|
focusTarget.focus();
|
|
76
98
|
}
|
|
77
99
|
}
|
|
78
|
-
return Effect.void;
|
|
79
100
|
});
|
|
80
101
|
const trapFocusWithinDialog = (event, dialog) => {
|
|
81
102
|
const focusable = Array.fromIterable(dialog.querySelectorAll(FOCUSABLE_SELECTOR));
|
|
@@ -125,13 +146,10 @@ export const closeModal = (selector) => Effect.suspend(() => {
|
|
|
125
146
|
* Task.clickElement('#menu-item-2').pipe(Effect.ignore, Effect.as(CompletedClickItem()))
|
|
126
147
|
* ```
|
|
127
148
|
*/
|
|
128
|
-
export const clickElement = (selector) => Effect.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return Effect.void;
|
|
133
|
-
}
|
|
134
|
-
return Effect.fail(new ElementNotFound({ selector }));
|
|
149
|
+
export const clickElement = (selector) => Effect.gen(function* () {
|
|
150
|
+
yield* awaitNextFrame;
|
|
151
|
+
const element = yield* queryHTMLElement(selector);
|
|
152
|
+
element.click();
|
|
135
153
|
});
|
|
136
154
|
/**
|
|
137
155
|
* Scrolls an element into view by selector using `{ block: 'nearest' }`.
|
|
@@ -142,13 +160,10 @@ export const clickElement = (selector) => Effect.suspend(() => {
|
|
|
142
160
|
* Task.scrollIntoView('#active-item').pipe(Effect.ignore, Effect.as(CompletedScrollIntoView()))
|
|
143
161
|
* ```
|
|
144
162
|
*/
|
|
145
|
-
export const scrollIntoView = (selector) => Effect.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return Effect.void;
|
|
150
|
-
}
|
|
151
|
-
return Effect.fail(new ElementNotFound({ selector }));
|
|
163
|
+
export const scrollIntoView = (selector) => Effect.gen(function* () {
|
|
164
|
+
yield* awaitNextFrame;
|
|
165
|
+
const element = yield* queryHTMLElement(selector);
|
|
166
|
+
element.scrollIntoView({ block: 'nearest' });
|
|
152
167
|
});
|
|
153
168
|
/**
|
|
154
169
|
* Focuses the next or previous focusable element in the document relative to the element matching the given selector.
|
|
@@ -159,20 +174,17 @@ export const scrollIntoView = (selector) => Effect.suspend(() => {
|
|
|
159
174
|
* Task.advanceFocus('#menu-button', 'Next').pipe(Effect.ignore, Effect.as(CompletedAdvanceFocus()))
|
|
160
175
|
* ```
|
|
161
176
|
*/
|
|
162
|
-
export const advanceFocus = (selector, direction) => Effect.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return Effect.fail(new ElementNotFound({ selector }));
|
|
166
|
-
}
|
|
177
|
+
export const advanceFocus = (selector, direction) => Effect.gen(function* () {
|
|
178
|
+
yield* awaitNextFrame;
|
|
179
|
+
const reference = yield* queryHTMLElement(selector);
|
|
167
180
|
const focusableElements = Array.fromIterable(document.querySelectorAll(FOCUSABLE_SELECTOR));
|
|
168
181
|
const referenceElementIndex = Array.findFirstIndex(focusableElements, Equal.equals(reference));
|
|
169
182
|
if (Option.isNone(referenceElementIndex)) {
|
|
170
|
-
return Effect.fail(new ElementNotFound({ selector }));
|
|
183
|
+
return yield* Effect.fail(new ElementNotFound({ selector }));
|
|
171
184
|
}
|
|
172
185
|
const offsetReferenceElementIndex = M.value(direction).pipe(M.when('Next', () => Number.increment), M.when('Previous', () => Number.decrement), M.exhaustive)(referenceElementIndex.value);
|
|
173
186
|
const nextElement = Array.get(focusableElements, offsetReferenceElementIndex);
|
|
174
187
|
if (Option.isSome(nextElement)) {
|
|
175
188
|
nextElement.value.focus();
|
|
176
189
|
}
|
|
177
|
-
return Effect.void;
|
|
178
190
|
});
|