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.
- package/dist/task/dom.d.ts +6 -7
- package/dist/task/dom.d.ts.map +1 -1
- package/dist/task/dom.js +83 -102
- package/dist/ui/dialog/index.d.ts +2 -0
- package/dist/ui/dialog/index.d.ts.map +1 -1
- package/dist/ui/dialog/index.js +10 -2
- package/package.json +1 -1
package/dist/task/dom.d.ts
CHANGED
|
@@ -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
|
|
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
|
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,SAAS,CAAA;AAmBzC
|
|
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.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/dialog/index.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/dialog/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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)] : []),
|