foldkit 0.36.1 → 0.36.3

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.
@@ -2,7 +2,6 @@ import { Effect } from 'effect';
2
2
  import { ElementNotFound } from './error';
3
3
  /**
4
4
  * Focuses an element matching the given selector.
5
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to focus.
6
5
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
7
6
  *
8
7
  * @example
@@ -16,19 +15,22 @@ export declare const focus: (selector: string) => Effect.Effect<void, ElementNot
16
15
  * and Escape key handling. Uses `show()` instead of `showModal()` so that
17
16
  * DevTools (and any other high-z-index overlay) remains interactive — the
18
17
  * Dialog component provides its own backdrop, scroll locking, and transitions.
19
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to show.
20
18
  * Fails with `ElementNotFound` if the selector does not match an `HTMLDialogElement`.
21
19
  *
20
+ * Pass `focusSelector` to focus an element inside the dialog when it opens.
21
+ *
22
22
  * @example
23
23
  * ```typescript
24
24
  * Task.showModal('#my-dialog').pipe(Effect.ignore, Effect.as(CompletedDialogShow()))
25
+ * Task.showModal('#my-dialog', { focusSelector: '#search-input' }).pipe(Effect.ignore, Effect.as(CompletedDialogShow()))
25
26
  * ```
26
27
  */
27
- export declare const showModal: (selector: string) => Effect.Effect<void, ElementNotFound>;
28
+ export declare const showModal: (selector: string, options?: Readonly<{
29
+ focusSelector?: string;
30
+ }>) => Effect.Effect<void, ElementNotFound>;
28
31
  /**
29
32
  * Closes a dialog element using `.close()`.
30
33
  * Cleans up the keyboard handlers installed by `showModal`.
31
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to close.
32
34
  * Fails with `ElementNotFound` if the selector does not match an `HTMLDialogElement`.
33
35
  *
34
36
  * @example
@@ -39,7 +41,6 @@ export declare const showModal: (selector: string) => Effect.Effect<void, Elemen
39
41
  export declare const closeModal: (selector: string) => Effect.Effect<void, ElementNotFound>;
40
42
  /**
41
43
  * Programmatically clicks an element matching the given selector.
42
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to click.
43
44
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
44
45
  *
45
46
  * @example
@@ -50,7 +51,6 @@ export declare const closeModal: (selector: string) => Effect.Effect<void, Eleme
50
51
  export declare const clickElement: (selector: string) => Effect.Effect<void, ElementNotFound>;
51
52
  /**
52
53
  * Scrolls an element into view by selector using `{ block: 'nearest' }`.
53
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to scroll.
54
54
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
55
55
  *
56
56
  * @example
@@ -63,7 +63,6 @@ export declare const scrollIntoView: (selector: string) => Effect.Effect<void, E
63
63
  export type FocusDirection = 'Next' | 'Previous';
64
64
  /**
65
65
  * Focuses the next or previous focusable element in the document relative to the element matching the given selector.
66
- * Uses requestAnimationFrame to ensure the DOM is updated before querying focus order.
67
66
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
68
67
  *
69
68
  * @example
@@ -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,SAAS,CAAA;AAmBzC;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,GAAI,UAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAWxE,CAAA;AAEJ;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CA0ClC,CAAA;AAuBJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAiBlC,CAAA;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAWlC,CAAA;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAYlC,CAAA;AAEJ,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAA;AAEhD;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,WAAW,cAAc,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAuClC,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,SAAS,CAAA;AAmBzC;;;;;;;;GAQG;AACH,eAAO,MAAM,KAAK,GAAI,UAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAQxE,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,CAQlC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAQlC,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,CAqClC,CAAA"}
package/dist/task/dom.js CHANGED
@@ -13,7 +13,6 @@ const FOCUSABLE_SELECTOR = Array.join([
13
13
  ], ', ');
14
14
  /**
15
15
  * Focuses an element matching the given selector.
16
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to focus.
17
16
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
18
17
  *
19
18
  * @example
@@ -21,62 +20,62 @@ const FOCUSABLE_SELECTOR = Array.join([
21
20
  * Task.focus('#email-input').pipe(Effect.ignore, Effect.as(CompletedInputFocus()))
22
21
  * ```
23
22
  */
24
- export const focus = (selector) => Effect.async(resume => {
25
- requestAnimationFrame(() => {
26
- const element = document.querySelector(selector);
27
- if (element instanceof HTMLElement) {
28
- element.focus();
29
- resume(Effect.void);
30
- }
31
- else {
32
- resume(Effect.fail(new ElementNotFound({ selector })));
33
- }
34
- });
23
+ export const focus = (selector) => Effect.suspend(() => {
24
+ const element = document.querySelector(selector);
25
+ if (element instanceof HTMLElement) {
26
+ element.focus();
27
+ return Effect.void;
28
+ }
29
+ return Effect.fail(new ElementNotFound({ selector }));
35
30
  });
36
31
  /**
37
32
  * Opens a dialog element using `show()` with high z-index, focus trapping,
38
33
  * and Escape key handling. Uses `show()` instead of `showModal()` so that
39
34
  * DevTools (and any other high-z-index overlay) remains interactive — the
40
35
  * Dialog component provides its own backdrop, scroll locking, and transitions.
41
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to show.
42
36
  * Fails with `ElementNotFound` if the selector does not match an `HTMLDialogElement`.
43
37
  *
38
+ * Pass `focusSelector` to focus an element inside the dialog when it opens.
39
+ *
44
40
  * @example
45
41
  * ```typescript
46
42
  * Task.showModal('#my-dialog').pipe(Effect.ignore, Effect.as(CompletedDialogShow()))
43
+ * Task.showModal('#my-dialog', { focusSelector: '#search-input' }).pipe(Effect.ignore, Effect.as(CompletedDialogShow()))
47
44
  * ```
48
45
  */
49
- export const showModal = (selector) => Effect.async(resume => {
50
- requestAnimationFrame(() => {
51
- const element = document.querySelector(selector);
52
- if (element instanceof HTMLDialogElement) {
53
- element.style.position = 'fixed';
54
- element.style.inset = '0';
55
- openDialogCount++;
56
- element.style.zIndex = String(BASE_DIALOG_Z_INDEX + openDialogCount);
57
- element.show();
58
- const handleKeydown = (event) => {
59
- if (!element.open) {
60
- return;
61
- }
62
- M.value(event.key).pipe(M.when('Escape', () => {
63
- if (event.defaultPrevented) {
64
- return;
65
- }
66
- event.preventDefault();
67
- element.dispatchEvent(new Event('cancel', { cancelable: true }));
68
- }), M.when('Tab', () => {
69
- trapFocusWithinDialog(event, element);
70
- }), M.orElse(Function.constVoid));
71
- };
72
- document.addEventListener('keydown', handleKeydown);
73
- dialogCleanups.set(element, () => document.removeEventListener('keydown', handleKeydown));
74
- resume(Effect.void);
46
+ export const showModal = (selector, options) => Effect.suspend(() => {
47
+ const element = document.querySelector(selector);
48
+ if (!(element instanceof HTMLDialogElement)) {
49
+ return Effect.fail(new ElementNotFound({ selector }));
50
+ }
51
+ element.style.position = 'fixed';
52
+ element.style.inset = '0';
53
+ openDialogCount++;
54
+ element.style.zIndex = String(BASE_DIALOG_Z_INDEX + openDialogCount);
55
+ element.show();
56
+ const handleKeydown = (event) => {
57
+ if (!element.open) {
58
+ return;
75
59
  }
76
- else {
77
- resume(Effect.fail(new ElementNotFound({ selector })));
60
+ M.value(event.key).pipe(M.when('Escape', () => {
61
+ if (event.defaultPrevented) {
62
+ return;
63
+ }
64
+ event.preventDefault();
65
+ element.dispatchEvent(new Event('cancel', { cancelable: true }));
66
+ }), M.when('Tab', () => {
67
+ trapFocusWithinDialog(event, element);
68
+ }), M.orElse(Function.constVoid));
69
+ };
70
+ document.addEventListener('keydown', handleKeydown);
71
+ dialogCleanups.set(element, () => document.removeEventListener('keydown', handleKeydown));
72
+ if (options?.focusSelector) {
73
+ const focusTarget = element.querySelector(options.focusSelector);
74
+ if (focusTarget instanceof HTMLElement) {
75
+ focusTarget.focus();
78
76
  }
79
- });
77
+ }
78
+ return Effect.void;
80
79
  });
81
80
  const trapFocusWithinDialog = (event, dialog) => {
82
81
  const focusable = Array.fromIterable(dialog.querySelectorAll(FOCUSABLE_SELECTOR));
@@ -96,7 +95,6 @@ const trapFocusWithinDialog = (event, dialog) => {
96
95
  /**
97
96
  * Closes a dialog element using `.close()`.
98
97
  * Cleans up the keyboard handlers installed by `showModal`.
99
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to close.
100
98
  * Fails with `ElementNotFound` if the selector does not match an `HTMLDialogElement`.
101
99
  *
102
100
  * @example
@@ -104,27 +102,22 @@ const trapFocusWithinDialog = (event, dialog) => {
104
102
  * Task.closeModal('#my-dialog').pipe(Effect.ignore, Effect.as(CompletedDialogClose()))
105
103
  * ```
106
104
  */
107
- export const closeModal = (selector) => Effect.async(resume => {
108
- requestAnimationFrame(() => {
109
- const element = document.querySelector(selector);
110
- if (element instanceof HTMLDialogElement) {
111
- element.close();
112
- openDialogCount = Math.max(0, openDialogCount - 1);
113
- const cleanup = dialogCleanups.get(element);
114
- if (cleanup) {
115
- cleanup();
116
- dialogCleanups.delete(element);
117
- }
118
- resume(Effect.void);
105
+ export const closeModal = (selector) => Effect.suspend(() => {
106
+ const element = document.querySelector(selector);
107
+ if (element instanceof HTMLDialogElement) {
108
+ element.close();
109
+ openDialogCount = Math.max(0, openDialogCount - 1);
110
+ const cleanup = dialogCleanups.get(element);
111
+ if (cleanup) {
112
+ cleanup();
113
+ dialogCleanups.delete(element);
119
114
  }
120
- else {
121
- resume(Effect.fail(new ElementNotFound({ selector })));
122
- }
123
- });
115
+ return Effect.void;
116
+ }
117
+ return Effect.fail(new ElementNotFound({ selector }));
124
118
  });
125
119
  /**
126
120
  * Programmatically clicks an element matching the given selector.
127
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to click.
128
121
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
129
122
  *
130
123
  * @example
@@ -132,21 +125,16 @@ export const closeModal = (selector) => Effect.async(resume => {
132
125
  * Task.clickElement('#menu-item-2').pipe(Effect.ignore, Effect.as(CompletedItemClick()))
133
126
  * ```
134
127
  */
135
- export const clickElement = (selector) => Effect.async(resume => {
136
- requestAnimationFrame(() => {
137
- const element = document.querySelector(selector);
138
- if (element instanceof HTMLElement) {
139
- element.click();
140
- resume(Effect.void);
141
- }
142
- else {
143
- resume(Effect.fail(new ElementNotFound({ selector })));
144
- }
145
- });
128
+ export const clickElement = (selector) => Effect.suspend(() => {
129
+ const element = document.querySelector(selector);
130
+ if (element instanceof HTMLElement) {
131
+ element.click();
132
+ return Effect.void;
133
+ }
134
+ return Effect.fail(new ElementNotFound({ selector }));
146
135
  });
147
136
  /**
148
137
  * Scrolls an element into view by selector using `{ block: 'nearest' }`.
149
- * Uses requestAnimationFrame to ensure the DOM is updated before attempting to scroll.
150
138
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
151
139
  *
152
140
  * @example
@@ -154,21 +142,16 @@ export const clickElement = (selector) => Effect.async(resume => {
154
142
  * Task.scrollIntoView('#active-item').pipe(Effect.ignore, Effect.as(CompletedScrollIntoView()))
155
143
  * ```
156
144
  */
157
- export const scrollIntoView = (selector) => Effect.async(resume => {
158
- requestAnimationFrame(() => {
159
- const element = document.querySelector(selector);
160
- if (element instanceof HTMLElement) {
161
- element.scrollIntoView({ block: 'nearest' });
162
- resume(Effect.void);
163
- }
164
- else {
165
- resume(Effect.fail(new ElementNotFound({ selector })));
166
- }
167
- });
145
+ export const scrollIntoView = (selector) => Effect.suspend(() => {
146
+ const element = document.querySelector(selector);
147
+ if (element instanceof HTMLElement) {
148
+ element.scrollIntoView({ block: 'nearest' });
149
+ return Effect.void;
150
+ }
151
+ return Effect.fail(new ElementNotFound({ selector }));
168
152
  });
169
153
  /**
170
154
  * Focuses the next or previous focusable element in the document relative to the element matching the given selector.
171
- * Uses requestAnimationFrame to ensure the DOM is updated before querying focus order.
172
155
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
173
156
  *
174
157
  * @example
@@ -176,22 +159,20 @@ export const scrollIntoView = (selector) => Effect.async(resume => {
176
159
  * Task.advanceFocus('#menu-button', 'Next').pipe(Effect.ignore, Effect.as(CompletedFocusAdvance()))
177
160
  * ```
178
161
  */
179
- export const advanceFocus = (selector, direction) => Effect.async(resume => {
180
- requestAnimationFrame(() => {
181
- const reference = document.querySelector(selector);
182
- if (!(reference instanceof HTMLElement)) {
183
- return resume(Effect.fail(new ElementNotFound({ selector })));
184
- }
185
- const focusableElements = Array.fromIterable(document.querySelectorAll(FOCUSABLE_SELECTOR));
186
- const referenceElementIndex = Array.findFirstIndex(focusableElements, Equal.equals(reference));
187
- if (Option.isNone(referenceElementIndex)) {
188
- return resume(Effect.fail(new ElementNotFound({ selector })));
189
- }
190
- const offsetReferenceElementIndex = M.value(direction).pipe(M.when('Next', () => Number.increment), M.when('Previous', () => Number.decrement), M.exhaustive)(referenceElementIndex.value);
191
- const nextElement = Array.get(focusableElements, offsetReferenceElementIndex);
192
- if (Option.isSome(nextElement)) {
193
- nextElement.value.focus();
194
- }
195
- resume(Effect.void);
196
- });
162
+ export const advanceFocus = (selector, direction) => Effect.suspend(() => {
163
+ const reference = document.querySelector(selector);
164
+ if (!(reference instanceof HTMLElement)) {
165
+ return Effect.fail(new ElementNotFound({ selector }));
166
+ }
167
+ const focusableElements = Array.fromIterable(document.querySelectorAll(FOCUSABLE_SELECTOR));
168
+ const referenceElementIndex = Array.findFirstIndex(focusableElements, Equal.equals(reference));
169
+ if (Option.isNone(referenceElementIndex)) {
170
+ return Effect.fail(new ElementNotFound({ selector }));
171
+ }
172
+ const offsetReferenceElementIndex = M.value(direction).pipe(M.when('Next', () => Number.increment), M.when('Previous', () => Number.decrement), M.exhaustive)(referenceElementIndex.value);
173
+ const nextElement = Array.get(focusableElements, offsetReferenceElementIndex);
174
+ if (Option.isSome(nextElement)) {
175
+ nextElement.value.focus();
176
+ }
177
+ return Effect.void;
197
178
  });
@@ -7,6 +7,7 @@ export declare const Model: S.Struct<{
7
7
  isOpen: typeof S.Boolean;
8
8
  isAnimated: typeof S.Boolean;
9
9
  transitionState: S.Literal<["Idle", "EnterStart", "EnterAnimating", "LeaveStart", "LeaveAnimating"]>;
10
+ maybeFocusSelector: S.OptionFromSelf<typeof S.String>;
10
11
  }>;
11
12
  export type Model = typeof Model.Type;
12
13
  /** Sent when the dialog should open. Triggers the showModal command. */
@@ -40,6 +41,7 @@ export type InitConfig = Readonly<{
40
41
  id: string;
41
42
  isOpen?: boolean;
42
43
  isAnimated?: boolean;
44
+ focusSelector?: string;
43
45
  }>;
44
46
  /** Creates an initial dialog model from a config. Defaults to closed and non-animated. */
45
47
  export declare const init: (config: InitConfig) => Model;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/dialog/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAEvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAE5C,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,IAAI,EAAoB,MAAM,YAAY,CAAA;AAQxE,oIAAoI;AACpI,eAAO,MAAM,KAAK;;;;;EAKhB,CAAA;AAEF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,wEAAwE;AACxE,eAAO,MAAM,MAAM,2DAAc,CAAA;AACjC,wHAAwH;AACxH,eAAO,MAAM,MAAM,2DAAc,CAAA;AACjC,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,wEAA2B,CAAA;AAC3D,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,yEAA4B,CAAA;AAC7D,oGAAoG;AACpG,eAAO,MAAM,uBAAuB,4EAA+B,CAAA;AACnE,mFAAmF;AACnF,eAAO,MAAM,eAAe,oEAAuB,CAAA;AAEnD,8DAA8D;AAC9D,eAAO,MAAM,OAAO,EAAE,CAAC,CAAC,KAAK,CAC3B;IACE,OAAO,MAAM;IACb,OAAO,MAAM;IACb,OAAO,mBAAmB;IAC1B,OAAO,oBAAoB;IAC3B,OAAO,uBAAuB;IAC9B,OAAO,eAAe;CACvB,CAQF,CAAA;AAED,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;AACvC,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;AACvC,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAA;AACjE,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAA;AAEnE,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAIzC,iIAAiI;AACjI,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAC,CAAA;AAEF,0FAA0F;AAC1F,eAAO,MAAM,IAAI,GAAI,QAAQ,UAAU,KAAG,KAKxC,CAAA;AAOF,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;AAG5D,0EAA0E;AAC1E,eAAO,MAAM,MAAM,GAAI,OAAO,KAAK,EAAE,SAAS,OAAO,KAAG,YAyGvD,CAAA;AAID,iGAAiG;AACjG,eAAO,MAAM,OAAO,GAAI,OAAO,KAAK,KAAG,MAA6B,CAAA;AAEpE,wGAAwG;AACxG,eAAO,MAAM,aAAa,GAAI,OAAO,KAAK,KAAG,MAAmC,CAAA;AAEhF,wDAAwD;AACxD,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,KAAK,EAAE,KAAK,CAAA;IACZ,SAAS,EAAE,CACT,OAAO,EAAE,MAAM,GAAG,mBAAmB,GAAG,oBAAoB,KACzD,OAAO,CAAA;IACZ,YAAY,EAAE,IAAI,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,eAAe,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACnD,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,kBAAkB,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACtD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;CAC/C,CAAC,CAAA;AAEF,sGAAsG;AACtG,eAAO,MAAM,IAAI,GAAI,OAAO,EAAE,QAAQ,UAAU,CAAC,OAAO,CAAC,KAAG,IA+F3D,CAAA;AAED;6EAC6E;AAC7E,eAAO,MAAM,IAAI,GAAI,OAAO,EAC1B,cAAc,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC,KAC7D,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,KAAK,IAAI,CAgBtE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/dialog/index.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ export const Model = S.Struct({
12
12
  isOpen: S.Boolean,
13
13
  isAnimated: S.Boolean,
14
14
  transitionState: TransitionState,
15
+ maybeFocusSelector: S.OptionFromSelf(S.String),
15
16
  });
16
17
  // MESSAGE
17
18
  /** Sent when the dialog should open. Triggers the showModal command. */
@@ -34,6 +35,7 @@ export const init = (config) => ({
34
35
  isOpen: config.isOpen ?? false,
35
36
  isAnimated: config.isAnimated ?? false,
36
37
  transitionState: 'Idle',
38
+ maybeFocusSelector: Option.fromNullable(config.focusSelector),
37
39
  });
38
40
  // UPDATE
39
41
  const dialogSelector = (id) => `#${id}`;
@@ -44,7 +46,11 @@ export const update = (model, message) => {
44
46
  const maybeNextFrame = OptionExt.when(model.isAnimated, Task.nextFrame.pipe(Effect.as(AdvancedTransitionFrame())));
45
47
  return M.value(message).pipe(withUpdateReturn, M.tagsExhaustive({
46
48
  Opened: () => {
47
- const maybeShow = Option.liftPredicate(Task.lockScroll.pipe(Effect.andThen(() => Task.showModal(dialogSelector(model.id))), Effect.ignore, Effect.as(CompletedDialogShow())), () => !model.isOpen);
49
+ const focusOptions = Option.match(model.maybeFocusSelector, {
50
+ onNone: () => undefined,
51
+ onSome: focusSelector => ({ focusSelector }),
52
+ });
53
+ const maybeShow = Option.liftPredicate(Task.lockScroll.pipe(Effect.andThen(() => Task.showModal(dialogSelector(model.id), focusOptions)), Effect.ignore, Effect.as(CompletedDialogShow())), () => !model.isOpen);
48
54
  return [
49
55
  evo(model, {
50
56
  isOpen: () => true,
@@ -127,7 +133,8 @@ export const view = (config) => {
127
133
  AriaDescribedBy(`${id}-description`),
128
134
  OnCancel(toMessage(Closed())),
129
135
  Style({
130
- overflow: 'hidden',
136
+ width: '100%',
137
+ height: '100%',
131
138
  maxWidth: '100%',
132
139
  maxHeight: '100%',
133
140
  padding: '0',
@@ -139,6 +146,7 @@ export const view = (config) => {
139
146
  ...attributes,
140
147
  ];
141
148
  const backdrop = keyed('div')(`${id}-backdrop`, [
149
+ Style({ minHeight: '100vh' }),
142
150
  ...transitionAttributes,
143
151
  ...(isLeaving ? [] : [OnClick(toMessage(Closed()))]),
144
152
  ...(backdropClassName ? [Class(backdropClassName)] : []),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit",
3
- "version": "0.36.1",
3
+ "version": "0.36.3",
4
4
  "description": "A frontend framework for TypeScript, built on Effect, using The Elm Architecture",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",