vanilla-jui 1.3.11 → 1.4.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/index.d.ts +2927 -0
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +4 -3
- package/src/components/accordion.ts +522 -0
- package/src/components/drop.ts +497 -0
- package/src/components/flow.ts +1579 -0
- package/src/components/form.ts +953 -0
- package/src/components/{icons.js → icons.ts} +24 -11
- package/src/components/menu.ts +615 -0
- package/src/components/{modal.js → modal.ts} +560 -237
- package/src/components/offcanvas.ts +496 -0
- package/src/components/{pagination.js → pagination.ts} +251 -76
- package/src/components/parabola.ts +335 -0
- package/src/components/{sticky.js → sticky.ts} +123 -98
- package/src/components/{swiper.js → swiper.ts} +510 -232
- package/src/components/tabs.ts +984 -0
- package/src/components/theme.ts +437 -0
- package/src/components/toast.ts +371 -0
- package/src/components/toc.ts +385 -0
- package/src/components/tooltip.ts +179 -0
- package/src/components/validator.ts +596 -0
- package/src/core/{Component.js → Component.ts} +134 -27
- package/src/css/animation.css +31 -1
- package/src/css/card.css +1 -1
- package/src/css/color.css +863 -1013
- package/src/css/color_p3.css +822 -975
- package/src/css/components.css +1 -0
- package/src/css/form.css +1 -1
- package/src/css/icon.css +17 -1
- package/src/css/index.css +2 -2
- package/src/css/menu.css +1 -1
- package/src/css/tabs.css +1 -0
- package/src/css/tokens.css +1 -1
- package/src/index.ts +37 -0
- package/src/locales/{index.js → index.ts} +1 -2
- package/src/types.d.ts +1 -0
- package/src/utilities/browser.ts +131 -0
- package/src/utilities/cache.ts +57 -0
- package/src/utilities/core.ts +614 -0
- package/src/utilities/dom.ts +468 -0
- package/src/utilities/events.ts +106 -0
- package/src/utilities/http.ts +35 -0
- package/src/utilities/storage.ts +46 -0
- package/src/components/accordion.js +0 -373
- package/src/components/drop.js +0 -437
- package/src/components/flow.js +0 -1133
- package/src/components/form.js +0 -585
- package/src/components/menu.js +0 -422
- package/src/components/offcanvas.js +0 -332
- package/src/components/parabola.js +0 -295
- package/src/components/tabs.js +0 -746
- package/src/components/theme.js +0 -353
- package/src/components/toast.js +0 -329
- package/src/components/toc.js +0 -226
- package/src/components/tooltip.js +0 -86
- package/src/components/validator.js +0 -586
- package/src/index.js +0 -45
- package/src/locales/index.min.js +0 -1
- package/src/utilities/browser.js +0 -111
- package/src/utilities/cache.js +0 -159
- package/src/utilities/core.js +0 -502
- package/src/utilities/debug.js +0 -103
- package/src/utilities/dom.js +0 -313
- package/src/utilities/events.js +0 -69
- package/src/utilities/http.js +0 -30
- package/src/utilities/legacy-dom.js +0 -179
- package/src/utilities/storage.js +0 -46
- /package/src/{components/components.js → css/themes/index.css} +0 -0
- /package/src/icons/{index.js → index.ts} +0 -0
|
@@ -9,78 +9,274 @@ import {
|
|
|
9
9
|
untrack,
|
|
10
10
|
} from 'vanilla-signal';
|
|
11
11
|
|
|
12
|
-
import Component
|
|
12
|
+
import Component, {
|
|
13
|
+
type ComponentDOM,
|
|
14
|
+
type ComponentRuntime,
|
|
15
|
+
type ComponentUpdateOptions,
|
|
16
|
+
} from '../core/Component.ts';
|
|
13
17
|
import {
|
|
18
|
+
type ResolveSchema,
|
|
14
19
|
hasOwn,
|
|
15
20
|
isPlainObject,
|
|
16
21
|
randomId,
|
|
17
22
|
resolveProps,
|
|
18
23
|
validateParam,
|
|
19
|
-
} from '../utilities/core.
|
|
24
|
+
} from '../utilities/core.ts';
|
|
20
25
|
import {
|
|
21
26
|
all,
|
|
22
27
|
q,
|
|
23
|
-
canUseDOM,
|
|
24
|
-
canRenderDOM,
|
|
25
28
|
createLoading,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
import {
|
|
29
|
+
isRenderableContent,
|
|
30
|
+
normalizeContentNodes,
|
|
31
|
+
type RenderableContent,
|
|
32
|
+
} from '../utilities/dom.ts';
|
|
33
|
+
import {
|
|
34
|
+
Form,
|
|
35
|
+
type FormDataRecord,
|
|
36
|
+
type FormField,
|
|
37
|
+
type FormProps,
|
|
38
|
+
} from './form.ts';
|
|
39
|
+
import { icon } from './icons.ts';
|
|
31
40
|
|
|
32
41
|
const HIDE_DURATION = 300;
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
type ModalStyle =
|
|
44
|
+
| string
|
|
45
|
+
| Record<string, string | number | null | undefined>
|
|
46
|
+
| null;
|
|
47
|
+
type ModalTextInput = Partial<ModalText> & Record<string, unknown>;
|
|
48
|
+
type ModalContent = RenderableContent<Modal>;
|
|
49
|
+
type FlowDirection = 'next' | 'back';
|
|
50
|
+
|
|
51
|
+
export interface ModalClassNames {
|
|
52
|
+
layout: string;
|
|
53
|
+
modal: string;
|
|
54
|
+
fullscreen: string;
|
|
55
|
+
header: string;
|
|
56
|
+
body: string;
|
|
57
|
+
footer: string;
|
|
58
|
+
title: string;
|
|
59
|
+
close: string;
|
|
60
|
+
cancel: string;
|
|
61
|
+
confirm: string;
|
|
62
|
+
button: string;
|
|
63
|
+
buttonIcon: string;
|
|
64
|
+
buttonGhost: string;
|
|
65
|
+
buttonPrimary: string;
|
|
66
|
+
buttonSmall: string;
|
|
67
|
+
formContainer: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type ModalClassNameConfig = Partial<ModalClassNames>;
|
|
71
|
+
|
|
72
|
+
export interface ModalText {
|
|
73
|
+
title: string;
|
|
74
|
+
confirm: string;
|
|
75
|
+
cancel: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface FlowStep {
|
|
79
|
+
id?: string;
|
|
80
|
+
title?: RenderableContent<Modal>;
|
|
81
|
+
content?: ModalContent;
|
|
82
|
+
modal?: ModalFlowView | ModalFlowViewFactory;
|
|
83
|
+
view?: ModalFlowView | ModalFlowViewFactory;
|
|
84
|
+
[key: string]: unknown;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface FlowSnapshot {
|
|
88
|
+
data?: Record<string, unknown> | null;
|
|
89
|
+
currentData?: Record<string, unknown> | null;
|
|
90
|
+
currentStep?: FlowStep;
|
|
91
|
+
[key: string]: unknown;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface FlowLike {
|
|
95
|
+
currentStep?: FlowStep;
|
|
96
|
+
next: (
|
|
97
|
+
payload: FormDataRecord | null
|
|
98
|
+
) => Promise<FlowSnapshot> | FlowSnapshot;
|
|
99
|
+
back: (
|
|
100
|
+
payload: FormDataRecord | null
|
|
101
|
+
) => Promise<FlowSnapshot> | FlowSnapshot;
|
|
102
|
+
snapshot: () => FlowSnapshot;
|
|
103
|
+
reset?: () => void;
|
|
104
|
+
destroy?: () => void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface ModalFlowView extends Partial<ModalProps> {
|
|
108
|
+
[key: string]: unknown;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
type ModalFlowViewFactory = (context: {
|
|
112
|
+
flow: FlowLike;
|
|
113
|
+
snapshot: FlowSnapshot | null;
|
|
114
|
+
step: FlowStep | undefined;
|
|
115
|
+
modal: Modal;
|
|
116
|
+
data: Record<string, unknown> | null;
|
|
117
|
+
currentData: Record<string, unknown> | null;
|
|
118
|
+
}) => unknown;
|
|
119
|
+
|
|
120
|
+
export interface ModalProps extends Record<string, unknown> {
|
|
121
|
+
content?: ModalContent;
|
|
122
|
+
position?: string;
|
|
123
|
+
showCancel?: boolean;
|
|
124
|
+
showClose?: boolean;
|
|
125
|
+
fullscreen?: boolean;
|
|
126
|
+
flow?: FlowLike | null;
|
|
127
|
+
text?: ModalTextInput;
|
|
128
|
+
onShow?: ((modal: Modal) => void | Promise<void>) | null;
|
|
129
|
+
onShown?: ((modal: Modal) => void | Promise<void>) | null;
|
|
130
|
+
onHide?: ((modal: Modal) => void | Promise<void>) | null;
|
|
131
|
+
onHidden?: ((modal: Modal) => void | Promise<void>) | null;
|
|
132
|
+
onConfirm?: ((modal: Modal) => void | Promise<void>) | null;
|
|
133
|
+
onSubmit?:
|
|
134
|
+
| ((data: FormDataRecord, modal: Modal) => void | Promise<void>)
|
|
135
|
+
| null;
|
|
136
|
+
onCancel?: ((modal: Modal) => void | Promise<void>) | null;
|
|
137
|
+
fields?: readonly FormField[] | null;
|
|
138
|
+
header?: boolean;
|
|
139
|
+
footer?: boolean;
|
|
140
|
+
style?: ModalStyle;
|
|
141
|
+
id?: string | null;
|
|
142
|
+
escClose?: boolean;
|
|
143
|
+
bgClose?: boolean;
|
|
144
|
+
lazy?: boolean;
|
|
145
|
+
className?: ModalClassNameConfig;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface ResolvedModalProps extends Record<string, unknown> {
|
|
149
|
+
content: ModalContent;
|
|
150
|
+
position: string;
|
|
151
|
+
showCancel: boolean;
|
|
152
|
+
showClose: boolean;
|
|
153
|
+
fullscreen: boolean;
|
|
154
|
+
flow: FlowLike | null;
|
|
155
|
+
text: ModalText;
|
|
156
|
+
onShow: NonNullable<ModalProps['onShow']> | null;
|
|
157
|
+
onShown: NonNullable<ModalProps['onShown']> | null;
|
|
158
|
+
onHide: NonNullable<ModalProps['onHide']> | null;
|
|
159
|
+
onHidden: NonNullable<ModalProps['onHidden']> | null;
|
|
160
|
+
onConfirm: NonNullable<ModalProps['onConfirm']> | null;
|
|
161
|
+
onSubmit: NonNullable<ModalProps['onSubmit']> | null;
|
|
162
|
+
onCancel: NonNullable<ModalProps['onCancel']> | null;
|
|
163
|
+
fields: FormField[] | null;
|
|
164
|
+
header: boolean;
|
|
165
|
+
footer: boolean;
|
|
166
|
+
style: ModalStyle;
|
|
167
|
+
id: string;
|
|
168
|
+
escClose: boolean;
|
|
169
|
+
bgClose: boolean;
|
|
170
|
+
lazy: boolean;
|
|
171
|
+
className: ModalClassNames;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface ModalState extends ResolvedModalProps {
|
|
175
|
+
loading: boolean;
|
|
176
|
+
submitting: boolean;
|
|
177
|
+
visible: boolean;
|
|
178
|
+
data: FormDataRecord | null;
|
|
179
|
+
extraData: FormDataRecord | null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface ModalDOM extends ComponentDOM {
|
|
183
|
+
root: HTMLElement | null;
|
|
184
|
+
modal: HTMLElement | null;
|
|
185
|
+
header: HTMLElement | null;
|
|
186
|
+
body: HTMLElement | null;
|
|
187
|
+
footer: HTMLElement | null;
|
|
188
|
+
form: Form | null;
|
|
189
|
+
formContainer: HTMLElement | null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface ModalRuntime extends ComponentRuntime {
|
|
193
|
+
scrollLocked: boolean;
|
|
194
|
+
visibleApplied: boolean;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface ModalCache {
|
|
198
|
+
initial: ResolvedModalProps | null;
|
|
199
|
+
fieldIds: Map<string, string> | null;
|
|
200
|
+
baseStyle: string;
|
|
201
|
+
previousActiveElement: HTMLElement | null;
|
|
202
|
+
formId: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
interface ModalCleanupExtras {
|
|
206
|
+
visibility?: (() => void) | null;
|
|
207
|
+
view?: (() => void) | null;
|
|
208
|
+
hideTimer?: ReturnType<typeof setTimeout> | null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
type ModalPatch = Partial<ModalProps>;
|
|
212
|
+
type ModalStatePatch = Partial<ModalState>;
|
|
213
|
+
|
|
214
|
+
const DEFAULT_CLASS_NAMES: ModalClassNames = {
|
|
215
|
+
layout: 'j-popup-layout',
|
|
216
|
+
modal: 'j-modal',
|
|
217
|
+
fullscreen: 'is-fullscreen',
|
|
218
|
+
header: 'modal-header',
|
|
219
|
+
body: 'modal-body',
|
|
220
|
+
footer: 'modal-footer',
|
|
221
|
+
title: 'modal-title',
|
|
222
|
+
close: 'modal-close',
|
|
223
|
+
cancel: 'modal-cancel',
|
|
224
|
+
confirm: 'modal-confirm',
|
|
225
|
+
button: 'j-button',
|
|
226
|
+
buttonIcon: 'is-icon',
|
|
227
|
+
buttonGhost: 'is-ghost',
|
|
228
|
+
buttonPrimary: 'is-primary',
|
|
229
|
+
buttonSmall: 'is-sm',
|
|
230
|
+
formContainer: 'modal-form-container',
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
function cloneFields(
|
|
234
|
+
fields: readonly FormField[] | null | undefined
|
|
235
|
+
): FormField[] {
|
|
35
236
|
if (!Array.isArray(fields)) return [];
|
|
36
237
|
return fields.map((field) => ({
|
|
37
238
|
...field,
|
|
38
239
|
options: Array.isArray(field.options)
|
|
39
|
-
? field.options.map(
|
|
40
|
-
option
|
|
240
|
+
? field.options.map(
|
|
241
|
+
(option: NonNullable<FormField['options']>[number]) =>
|
|
242
|
+
option && typeof option === 'object' ? { ...option } : option
|
|
41
243
|
)
|
|
42
244
|
: field.options,
|
|
43
245
|
}));
|
|
44
246
|
}
|
|
45
247
|
|
|
46
|
-
function cloneProps(props) {
|
|
248
|
+
function cloneProps(props: ResolvedModalProps): ResolvedModalProps {
|
|
47
249
|
return {
|
|
48
250
|
...props,
|
|
49
|
-
fields: Array.isArray(props.fields)
|
|
50
|
-
|
|
51
|
-
|
|
251
|
+
fields: Array.isArray(props.fields) ? cloneFields(props.fields) : null,
|
|
252
|
+
text: { ...props.text },
|
|
253
|
+
className: { ...props.className },
|
|
52
254
|
};
|
|
53
255
|
}
|
|
54
256
|
|
|
55
|
-
function
|
|
56
|
-
return (
|
|
57
|
-
content == null ||
|
|
58
|
-
typeof content === 'string' ||
|
|
59
|
-
typeof content === 'function' ||
|
|
60
|
-
Array.isArray(content) ||
|
|
61
|
-
isNode(content)
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function isUpdateProps(value) {
|
|
257
|
+
function isUpdateProps(value: unknown): value is ModalPatch {
|
|
66
258
|
return isPlainObject(value);
|
|
67
259
|
}
|
|
68
260
|
|
|
69
|
-
function isFlowLike(value) {
|
|
261
|
+
function isFlowLike(value: unknown): value is FlowLike | null {
|
|
70
262
|
return (
|
|
71
263
|
value == null ||
|
|
72
264
|
(typeof value === 'object' &&
|
|
73
|
-
typeof value.next === 'function' &&
|
|
74
|
-
typeof value.back === 'function' &&
|
|
75
|
-
typeof value.snapshot === 'function')
|
|
265
|
+
typeof (value as Partial<FlowLike>).next === 'function' &&
|
|
266
|
+
typeof (value as Partial<FlowLike>).back === 'function' &&
|
|
267
|
+
typeof (value as Partial<FlowLike>).snapshot === 'function')
|
|
76
268
|
);
|
|
77
269
|
}
|
|
78
270
|
|
|
79
|
-
function hydrateFields(
|
|
80
|
-
|
|
271
|
+
function hydrateFields(
|
|
272
|
+
fields: readonly FormField[] | null,
|
|
273
|
+
data: Record<string, unknown> | null | undefined
|
|
274
|
+
): FormField[] | null {
|
|
275
|
+
if (!Array.isArray(fields) || !isPlainObject(data))
|
|
276
|
+
return fields ? cloneFields(fields) : null;
|
|
81
277
|
|
|
82
278
|
return fields.map((field) => {
|
|
83
|
-
if (!field?.name || !hasOwn(data, field.name)) return field;
|
|
279
|
+
if (!field?.name || !hasOwn(data, field.name)) return { ...field };
|
|
84
280
|
const value = data[field.name];
|
|
85
281
|
|
|
86
282
|
if (field.type === 'checkbox' || field.type === 'radio') {
|
|
@@ -91,11 +287,18 @@ function hydrateFields(fields, data) {
|
|
|
91
287
|
return { ...field, checked };
|
|
92
288
|
}
|
|
93
289
|
|
|
94
|
-
return {
|
|
290
|
+
return {
|
|
291
|
+
...field,
|
|
292
|
+
value: Array.isArray(value)
|
|
293
|
+
? value.map(String)
|
|
294
|
+
: (value as FormField['value']),
|
|
295
|
+
};
|
|
95
296
|
});
|
|
96
297
|
}
|
|
97
298
|
|
|
98
|
-
function createTextState(
|
|
299
|
+
function createTextState(
|
|
300
|
+
props: Partial<ModalProps> | Partial<ResolvedModalProps>
|
|
301
|
+
): ModalText {
|
|
99
302
|
const text = isPlainObject(props?.text) ? props.text : {};
|
|
100
303
|
return {
|
|
101
304
|
title: typeof text.title === 'string' ? text.title : 'Tip',
|
|
@@ -104,7 +307,7 @@ function createTextState(props) {
|
|
|
104
307
|
};
|
|
105
308
|
}
|
|
106
309
|
|
|
107
|
-
function createModalState(props) {
|
|
310
|
+
function createModalState(props: ResolvedModalProps): ModalState {
|
|
108
311
|
return {
|
|
109
312
|
...props,
|
|
110
313
|
fields: Array.isArray(props.fields) ? cloneFields(props.fields) : null,
|
|
@@ -117,25 +320,40 @@ function createModalState(props) {
|
|
|
117
320
|
};
|
|
118
321
|
}
|
|
119
322
|
|
|
120
|
-
function mergeExtraData(
|
|
323
|
+
function mergeExtraData(
|
|
324
|
+
data: FormDataRecord,
|
|
325
|
+
extraData: FormDataRecord | null
|
|
326
|
+
): FormDataRecord {
|
|
121
327
|
if (!extraData || typeof extraData !== 'object') return data;
|
|
122
328
|
return Object.assign(data, extraData);
|
|
123
329
|
}
|
|
124
330
|
|
|
125
|
-
function clonePlainObject(value) {
|
|
331
|
+
function clonePlainObject(value: unknown): ModalFlowView {
|
|
126
332
|
return isPlainObject(value) ? { ...value } : {};
|
|
127
333
|
}
|
|
128
334
|
|
|
335
|
+
function joinClasses(
|
|
336
|
+
...classes: Array<string | false | null | undefined>
|
|
337
|
+
): string {
|
|
338
|
+
return classes.filter(Boolean).join(' ');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function textValue(value: RenderableContent<Modal> | undefined): string {
|
|
342
|
+
return typeof value === 'string' || typeof value === 'number'
|
|
343
|
+
? String(value)
|
|
344
|
+
: '';
|
|
345
|
+
}
|
|
346
|
+
|
|
129
347
|
const MODAL_CONTENT_RULE = {
|
|
130
|
-
validate:
|
|
131
|
-
message: 'expects string, Node, array, function or null.',
|
|
348
|
+
validate: isRenderableContent,
|
|
349
|
+
message: 'expects string, number, Node, array, function or null.',
|
|
132
350
|
};
|
|
133
351
|
|
|
134
352
|
const MODAL_TEXT_RULE = {
|
|
135
353
|
default: {},
|
|
136
|
-
validate: (value) => isPlainObject(value),
|
|
354
|
+
validate: (value: unknown) => isPlainObject(value),
|
|
137
355
|
message: 'expects an object with text fields.',
|
|
138
|
-
normalize: (value) => {
|
|
356
|
+
normalize: (value: unknown) => {
|
|
139
357
|
const text = isPlainObject(value) ? value : {};
|
|
140
358
|
return {
|
|
141
359
|
...text,
|
|
@@ -173,9 +391,11 @@ const MODAL_PROPS_SCHEMA = {
|
|
|
173
391
|
id: {
|
|
174
392
|
default: null,
|
|
175
393
|
types: ['string', 'null'],
|
|
176
|
-
normalize: (value) => {
|
|
177
|
-
if (typeof value === 'string')
|
|
178
|
-
|
|
394
|
+
normalize: (value: unknown) => {
|
|
395
|
+
if (typeof value === 'string') {
|
|
396
|
+
const id = value.trim();
|
|
397
|
+
return id || randomId();
|
|
398
|
+
}
|
|
179
399
|
if (value == null) return randomId();
|
|
180
400
|
return value;
|
|
181
401
|
},
|
|
@@ -183,7 +403,15 @@ const MODAL_PROPS_SCHEMA = {
|
|
|
183
403
|
escClose: { default: false, type: 'boolean' },
|
|
184
404
|
bgClose: { default: false, type: 'boolean' },
|
|
185
405
|
lazy: { default: false, type: 'boolean' },
|
|
186
|
-
|
|
406
|
+
className: {
|
|
407
|
+
default: DEFAULT_CLASS_NAMES,
|
|
408
|
+
type: 'object',
|
|
409
|
+
normalize: (value: unknown) => ({
|
|
410
|
+
...DEFAULT_CLASS_NAMES,
|
|
411
|
+
...(value && typeof value === 'object' ? value : {}),
|
|
412
|
+
}),
|
|
413
|
+
},
|
|
414
|
+
} satisfies ResolveSchema<ModalProps>;
|
|
187
415
|
|
|
188
416
|
const MODAL_STATE_SCHEMA = {
|
|
189
417
|
...MODAL_PROPS_SCHEMA,
|
|
@@ -197,7 +425,7 @@ const MODAL_STATE_SCHEMA = {
|
|
|
197
425
|
const MODAL_UPDATE_BLOCKED_KEYS = new Set(['id', 'lazy']);
|
|
198
426
|
|
|
199
427
|
const MODAL_EXTRA_FIELDS_RULE = {
|
|
200
|
-
validate: (value) =>
|
|
428
|
+
validate: (value: unknown) =>
|
|
201
429
|
!!value && typeof value === 'object' && !Array.isArray(value),
|
|
202
430
|
message: 'expects an object.',
|
|
203
431
|
};
|
|
@@ -207,12 +435,48 @@ const MODAL_UPDATE_RULE = {
|
|
|
207
435
|
message: 'expects a props object.',
|
|
208
436
|
};
|
|
209
437
|
|
|
438
|
+
function normalizeProps(input: ModalProps): ResolvedModalProps {
|
|
439
|
+
const props = resolveProps(input, MODAL_PROPS_SCHEMA, 'Modal');
|
|
440
|
+
return {
|
|
441
|
+
content: props.content as ModalContent,
|
|
442
|
+
position: props.position as string,
|
|
443
|
+
showCancel: props.showCancel as boolean,
|
|
444
|
+
showClose: props.showClose as boolean,
|
|
445
|
+
fullscreen: props.fullscreen as boolean,
|
|
446
|
+
flow: props.flow as FlowLike | null,
|
|
447
|
+
text: props.text as ModalText,
|
|
448
|
+
onShow: props.onShow as ResolvedModalProps['onShow'],
|
|
449
|
+
onShown: props.onShown as ResolvedModalProps['onShown'],
|
|
450
|
+
onHide: props.onHide as ResolvedModalProps['onHide'],
|
|
451
|
+
onHidden: props.onHidden as ResolvedModalProps['onHidden'],
|
|
452
|
+
onConfirm: props.onConfirm as ResolvedModalProps['onConfirm'],
|
|
453
|
+
onSubmit: props.onSubmit as ResolvedModalProps['onSubmit'],
|
|
454
|
+
onCancel: props.onCancel as ResolvedModalProps['onCancel'],
|
|
455
|
+
fields: Array.isArray(props.fields)
|
|
456
|
+
? cloneFields(props.fields as FormField[])
|
|
457
|
+
: null,
|
|
458
|
+
header: props.header as boolean,
|
|
459
|
+
footer: props.footer as boolean,
|
|
460
|
+
style: props.style as ModalStyle,
|
|
461
|
+
id: props.id as string,
|
|
462
|
+
escClose: props.escClose as boolean,
|
|
463
|
+
bgClose: props.bgClose as boolean,
|
|
464
|
+
lazy: props.lazy as boolean,
|
|
465
|
+
className: props.className as ModalClassNames,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
210
469
|
let modalScrollLockCount = 0;
|
|
211
470
|
let modalBodyOverflow = '';
|
|
212
471
|
|
|
213
|
-
export class Modal extends Component {
|
|
214
|
-
|
|
215
|
-
|
|
472
|
+
export class Modal extends Component<ResolvedModalProps, ModalState, ModalDOM> {
|
|
473
|
+
declare runtime: ModalRuntime;
|
|
474
|
+
declare state: ModalState;
|
|
475
|
+
declare cleanup: Component['cleanup'] & ModalCleanupExtras;
|
|
476
|
+
cache: ModalCache;
|
|
477
|
+
|
|
478
|
+
constructor(input: ModalProps = {}) {
|
|
479
|
+
const props = normalizeProps(input);
|
|
216
480
|
super(props);
|
|
217
481
|
|
|
218
482
|
this.dom.modal = null;
|
|
@@ -237,38 +501,41 @@ export class Modal extends Component {
|
|
|
237
501
|
formId: `${this.props.id}_form`,
|
|
238
502
|
};
|
|
239
503
|
|
|
240
|
-
this.state = createDeepStore(createModalState(this.props));
|
|
504
|
+
this.state = createDeepStore(createModalState(this.props)) as ModalState;
|
|
241
505
|
|
|
242
506
|
this.init(props);
|
|
243
507
|
}
|
|
244
508
|
|
|
245
|
-
onInit() {
|
|
509
|
+
protected override onInit(): void {
|
|
246
510
|
this.bindReactiveVisibility();
|
|
247
|
-
if (!this.state.lazy
|
|
511
|
+
if (!this.state.lazy) this.buildRoot();
|
|
248
512
|
}
|
|
249
513
|
|
|
250
|
-
buildRoot() {
|
|
251
|
-
if (this.root) return this.root;
|
|
514
|
+
buildRoot(): HTMLElement {
|
|
515
|
+
if (this.root instanceof HTMLElement) return this.root;
|
|
252
516
|
|
|
253
|
-
const { id } = this.state;
|
|
517
|
+
const { id, className } = this.state;
|
|
254
518
|
const dialogChildren = [
|
|
255
519
|
jsx('div', {
|
|
256
|
-
className:
|
|
520
|
+
className: className.header,
|
|
521
|
+
'data-modal-header': id,
|
|
257
522
|
style: () => ({ display: this.state.header ? '' : 'none' }),
|
|
258
|
-
ref: (element) => {
|
|
523
|
+
ref: (element: HTMLElement) => {
|
|
259
524
|
this.dom.header = element;
|
|
260
525
|
},
|
|
261
526
|
}),
|
|
262
527
|
jsx('div', {
|
|
263
|
-
className:
|
|
264
|
-
|
|
528
|
+
className: className.body,
|
|
529
|
+
'data-modal-body': id,
|
|
530
|
+
ref: (element: HTMLElement) => {
|
|
265
531
|
this.dom.body = element;
|
|
266
532
|
},
|
|
267
533
|
}),
|
|
268
534
|
jsx('div', {
|
|
269
|
-
className:
|
|
535
|
+
className: className.footer,
|
|
536
|
+
'data-modal-footer': id,
|
|
270
537
|
style: () => ({ display: this.state.footer ? '' : 'none' }),
|
|
271
|
-
ref: (element) => {
|
|
538
|
+
ref: (element: HTMLElement) => {
|
|
272
539
|
this.dom.footer = element;
|
|
273
540
|
},
|
|
274
541
|
}),
|
|
@@ -276,32 +543,39 @@ export class Modal extends Component {
|
|
|
276
543
|
|
|
277
544
|
const modal = jsx('div', {
|
|
278
545
|
className: () =>
|
|
279
|
-
|
|
546
|
+
joinClasses(
|
|
547
|
+
this.state.className.modal,
|
|
548
|
+
this.state.fullscreen && this.state.className.fullscreen
|
|
549
|
+
),
|
|
280
550
|
id,
|
|
281
551
|
role: 'document',
|
|
282
|
-
|
|
552
|
+
'data-modal-dialog': id,
|
|
553
|
+
ref: (element: HTMLElement) => {
|
|
283
554
|
this.dom.modal = element;
|
|
284
555
|
this.applyStyle(element, this.state.style);
|
|
285
556
|
},
|
|
286
557
|
children: dialogChildren,
|
|
287
|
-
});
|
|
558
|
+
}) as HTMLElement;
|
|
288
559
|
|
|
289
560
|
const root = jsx('div', {
|
|
290
|
-
className: () =>
|
|
561
|
+
className: () =>
|
|
562
|
+
joinClasses(this.state.className.layout, `is-${this.state.position}`),
|
|
291
563
|
role: 'dialog',
|
|
564
|
+
'data-modal': 'root',
|
|
565
|
+
'data-modal-position': () => this.state.position,
|
|
292
566
|
'aria-modal': 'true',
|
|
293
567
|
'aria-labelledby': () => (this.state.header ? `${id}_title` : null),
|
|
294
568
|
'aria-label': () =>
|
|
295
569
|
this.state.header ? null : this.state.text?.title || 'Modal',
|
|
296
570
|
children: modal,
|
|
297
|
-
});
|
|
571
|
+
}) as HTMLElement;
|
|
298
572
|
|
|
299
573
|
this.root = root;
|
|
300
574
|
this.mountView();
|
|
301
575
|
return root;
|
|
302
576
|
}
|
|
303
577
|
|
|
304
|
-
mountView() {
|
|
578
|
+
mountView(): void {
|
|
305
579
|
if (this.cleanup.view || !this.dom.body) return;
|
|
306
580
|
|
|
307
581
|
this.cleanup.view = createRoot((dispose) => {
|
|
@@ -310,7 +584,7 @@ export class Modal extends Component {
|
|
|
310
584
|
onCleanup(headerDispose);
|
|
311
585
|
}
|
|
312
586
|
|
|
313
|
-
render(() => this.bodyView(), this.dom.body);
|
|
587
|
+
if (this.dom.body) render(() => this.bodyView(), this.dom.body);
|
|
314
588
|
|
|
315
589
|
if (this.dom.footer) {
|
|
316
590
|
const footerDispose = render(() => this.footerView(), this.dom.footer);
|
|
@@ -323,72 +597,88 @@ export class Modal extends Component {
|
|
|
323
597
|
});
|
|
324
598
|
}
|
|
325
599
|
|
|
326
|
-
headerView() {
|
|
600
|
+
headerView(): () => (Node | null)[] | null {
|
|
327
601
|
return () => {
|
|
328
602
|
if (!this.state.header) return null;
|
|
329
603
|
|
|
330
604
|
return [
|
|
331
605
|
jsx('div', {
|
|
332
|
-
className:
|
|
606
|
+
className: this.state.className.title,
|
|
333
607
|
id: `${this.state.id}_title`,
|
|
608
|
+
'data-modal-title': this.state.id,
|
|
334
609
|
children: () => this.state.text?.title,
|
|
335
|
-
}),
|
|
610
|
+
}) as Node,
|
|
336
611
|
this.state.showClose
|
|
337
|
-
? jsx('button', {
|
|
612
|
+
? (jsx('button', {
|
|
338
613
|
type: 'button',
|
|
339
|
-
className:
|
|
614
|
+
className: joinClasses(
|
|
615
|
+
this.state.className.button,
|
|
616
|
+
this.state.className.buttonIcon,
|
|
617
|
+
this.state.className.buttonGhost,
|
|
618
|
+
this.state.className.buttonSmall,
|
|
619
|
+
this.state.className.close
|
|
620
|
+
),
|
|
340
621
|
'data-action': 'close',
|
|
341
622
|
'aria-label': 'close',
|
|
342
623
|
children: icon('close'),
|
|
343
|
-
})
|
|
624
|
+
}) as Node)
|
|
344
625
|
: null,
|
|
345
626
|
];
|
|
346
627
|
};
|
|
347
628
|
}
|
|
348
629
|
|
|
349
|
-
footerView() {
|
|
630
|
+
footerView(): () => (Node | null)[] | null {
|
|
350
631
|
return () => {
|
|
351
632
|
if (!this.state.footer) return null;
|
|
352
633
|
|
|
353
634
|
return [
|
|
354
635
|
this.state.showCancel
|
|
355
|
-
? jsx('button', {
|
|
636
|
+
? (jsx('button', {
|
|
356
637
|
type: 'button',
|
|
357
|
-
className:
|
|
638
|
+
className: joinClasses(
|
|
639
|
+
this.state.className.button,
|
|
640
|
+
this.state.className.buttonGhost,
|
|
641
|
+
this.state.className.cancel
|
|
642
|
+
),
|
|
358
643
|
'data-action': 'close',
|
|
359
644
|
'aria-label': 'close',
|
|
360
645
|
disabled: () => this.isBusy(),
|
|
361
646
|
children: () => this.state.text?.cancel,
|
|
362
|
-
})
|
|
647
|
+
}) as Node)
|
|
363
648
|
: null,
|
|
364
649
|
jsx('button', {
|
|
365
650
|
type: () => (this.isFormMode() ? 'submit' : 'button'),
|
|
366
651
|
form: () => (this.isFormMode() ? this.cache.formId : null),
|
|
367
|
-
className:
|
|
368
|
-
|
|
652
|
+
className: joinClasses(
|
|
653
|
+
this.state.className.button,
|
|
654
|
+
this.state.className.buttonPrimary,
|
|
655
|
+
this.state.className.confirm
|
|
656
|
+
),
|
|
657
|
+
'data-action': () => (this.isFormMode() ? 'submit' : 'confirm'),
|
|
369
658
|
disabled: () => this.isBusy(),
|
|
370
659
|
children: () => this.state.text?.confirm,
|
|
371
|
-
}),
|
|
660
|
+
}) as Node,
|
|
372
661
|
];
|
|
373
662
|
};
|
|
374
663
|
}
|
|
375
664
|
|
|
376
|
-
bodyView() {
|
|
665
|
+
bodyView(): RenderableContent<Modal> {
|
|
377
666
|
if (this.isFormMode()) return this.formView();
|
|
378
667
|
this.destroyForm();
|
|
379
668
|
return this.contentView(this.state.content);
|
|
380
669
|
}
|
|
381
670
|
|
|
382
|
-
formView() {
|
|
671
|
+
formView(): HTMLElement {
|
|
383
672
|
return jsx('div', {
|
|
384
|
-
className:
|
|
385
|
-
|
|
673
|
+
className: this.state.className.formContainer,
|
|
674
|
+
'data-modal-form-container': this.state.id,
|
|
675
|
+
ref: (element: HTMLElement) => {
|
|
386
676
|
this.mountForm(element);
|
|
387
677
|
},
|
|
388
|
-
});
|
|
678
|
+
}) as HTMLElement;
|
|
389
679
|
}
|
|
390
680
|
|
|
391
|
-
mountForm(container) {
|
|
681
|
+
mountForm(container: HTMLElement | null): void {
|
|
392
682
|
if (!container) return;
|
|
393
683
|
this.dom.formContainer = container;
|
|
394
684
|
|
|
@@ -401,7 +691,7 @@ export class Modal extends Component {
|
|
|
401
691
|
this.dom.form = new Form(props, container).build();
|
|
402
692
|
}
|
|
403
693
|
|
|
404
|
-
createFormProps() {
|
|
694
|
+
createFormProps(): FormProps {
|
|
405
695
|
return {
|
|
406
696
|
id: this.cache.formId,
|
|
407
697
|
fields: this.state.fields || [],
|
|
@@ -410,31 +700,23 @@ export class Modal extends Component {
|
|
|
410
700
|
};
|
|
411
701
|
}
|
|
412
702
|
|
|
413
|
-
destroyForm() {
|
|
703
|
+
destroyForm(): void {
|
|
414
704
|
this.dom.form?.destroy();
|
|
415
705
|
this.dom.form = null;
|
|
416
706
|
this.dom.formContainer = null;
|
|
417
707
|
}
|
|
418
708
|
|
|
419
|
-
contentView(content) {
|
|
420
|
-
|
|
421
|
-
if (Array.isArray(content) || isNode(content)) return content;
|
|
422
|
-
if (content == null) return '';
|
|
423
|
-
if (typeof content !== 'string') return '';
|
|
424
|
-
|
|
425
|
-
// return Array.from(html(content).childNodes);
|
|
426
|
-
const template = document.createElement('template');
|
|
427
|
-
template.innerHTML = content;
|
|
428
|
-
return Array.from(template.content.childNodes);
|
|
709
|
+
contentView(content: ModalContent): Node[] {
|
|
710
|
+
return normalizeContentNodes(content, this);
|
|
429
711
|
}
|
|
430
712
|
|
|
431
|
-
bindReactiveLoading() {
|
|
432
|
-
let loading = null;
|
|
713
|
+
bindReactiveLoading(): void {
|
|
714
|
+
let loading: HTMLElement | null = null;
|
|
433
715
|
|
|
434
716
|
createEffect(() => {
|
|
435
717
|
if (this.state.loading && !loading) {
|
|
436
718
|
loading = createLoading();
|
|
437
|
-
this.dom.modal
|
|
719
|
+
this.dom.modal?.appendChild(loading);
|
|
438
720
|
} else if (!this.state.loading && loading) {
|
|
439
721
|
loading.remove();
|
|
440
722
|
loading = null;
|
|
@@ -447,22 +729,22 @@ export class Modal extends Component {
|
|
|
447
729
|
});
|
|
448
730
|
}
|
|
449
731
|
|
|
450
|
-
bindReactiveStyle() {
|
|
732
|
+
bindReactiveStyle(): void {
|
|
451
733
|
createEffect(() => {
|
|
452
734
|
const style = this.state.style;
|
|
453
735
|
if (this.dom.modal) this.applyStyle(this.dom.modal, style);
|
|
454
736
|
});
|
|
455
737
|
}
|
|
456
738
|
|
|
457
|
-
isFormMode() {
|
|
739
|
+
isFormMode(): boolean {
|
|
458
740
|
return Array.isArray(this.state.fields);
|
|
459
741
|
}
|
|
460
742
|
|
|
461
|
-
isBusy() {
|
|
743
|
+
isBusy(): boolean {
|
|
462
744
|
return !!(this.state.loading || this.state.submitting);
|
|
463
745
|
}
|
|
464
746
|
|
|
465
|
-
validatePropsPatch(patch, namespace = 'Modal.update') {
|
|
747
|
+
validatePropsPatch(patch: ModalPatch, namespace = 'Modal.update'): void {
|
|
466
748
|
validateParam('props', patch, MODAL_UPDATE_RULE, namespace);
|
|
467
749
|
|
|
468
750
|
for (const key of Object.keys(patch)) {
|
|
@@ -476,56 +758,75 @@ export class Modal extends Component {
|
|
|
476
758
|
`Modal.update: "${key}" cannot be updated after initialization.`
|
|
477
759
|
);
|
|
478
760
|
}
|
|
479
|
-
validateParam(
|
|
761
|
+
validateParam(
|
|
762
|
+
key,
|
|
763
|
+
patch[key],
|
|
764
|
+
MODAL_PROPS_SCHEMA[key as keyof typeof MODAL_PROPS_SCHEMA],
|
|
765
|
+
namespace
|
|
766
|
+
);
|
|
480
767
|
}
|
|
481
768
|
}
|
|
482
769
|
|
|
483
|
-
applyProps(
|
|
770
|
+
applyProps(
|
|
771
|
+
patch: ModalPatch,
|
|
772
|
+
{
|
|
773
|
+
validate = true,
|
|
774
|
+
force = false,
|
|
775
|
+
}: { validate?: boolean; force?: boolean } = {}
|
|
776
|
+
): this {
|
|
484
777
|
if (validate) this.validatePropsPatch(patch);
|
|
485
778
|
if (!patch || Object.keys(patch).length === 0) return this;
|
|
486
779
|
|
|
487
780
|
const hasFields = hasOwn(patch, 'fields');
|
|
488
781
|
const hasContent = hasOwn(patch, 'content');
|
|
489
782
|
|
|
490
|
-
if (hasContent) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
);
|
|
495
|
-
}
|
|
783
|
+
if (hasContent && !hasFields && this.isFormMode() && !force) {
|
|
784
|
+
throw new Error(
|
|
785
|
+
'Modal.update: Cannot update content when fields are defined.'
|
|
786
|
+
);
|
|
496
787
|
}
|
|
497
788
|
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
789
|
+
const nextInput: ModalProps = {
|
|
790
|
+
...(this.props as ModalProps),
|
|
791
|
+
...patch,
|
|
792
|
+
text: (hasOwn(patch, 'text')
|
|
793
|
+
? { ...this.props.text, ...patch.text }
|
|
794
|
+
: this.props.text) as ModalTextInput,
|
|
795
|
+
className: hasOwn(patch, 'className')
|
|
796
|
+
? { ...this.props.className, ...patch.className }
|
|
797
|
+
: this.props.className,
|
|
798
|
+
};
|
|
799
|
+
const nextProps = normalizeProps(nextInput);
|
|
502
800
|
|
|
503
801
|
this.props = nextProps;
|
|
504
|
-
super.update(patch
|
|
802
|
+
super.update(patch as Partial<ResolvedModalProps>, { force });
|
|
505
803
|
this.props = nextProps;
|
|
506
804
|
|
|
507
|
-
const statePatch = {};
|
|
805
|
+
const statePatch: ModalStatePatch = {};
|
|
508
806
|
if (hasFields) {
|
|
509
|
-
this.cache.fieldIds
|
|
807
|
+
this.cache.fieldIds?.clear();
|
|
510
808
|
statePatch.fields = Array.isArray(patch.fields)
|
|
511
809
|
? cloneFields(patch.fields)
|
|
512
810
|
: null;
|
|
513
811
|
}
|
|
514
812
|
|
|
515
813
|
for (const [key, value] of Object.entries(patch)) {
|
|
516
|
-
if (key === 'fields' || key === 'text') continue;
|
|
517
|
-
statePatch[key] =
|
|
814
|
+
if (key === 'fields' || key === 'text' || key === 'className') continue;
|
|
815
|
+
statePatch[key as keyof ModalStatePatch] =
|
|
816
|
+
value as ModalStatePatch[keyof ModalStatePatch];
|
|
518
817
|
}
|
|
519
818
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
819
|
+
if (hasOwn(patch, 'text')) {
|
|
820
|
+
statePatch.text = createTextState(nextProps);
|
|
821
|
+
}
|
|
822
|
+
if (hasOwn(patch, 'className')) {
|
|
823
|
+
statePatch.className = nextProps.className;
|
|
524
824
|
}
|
|
525
825
|
|
|
526
826
|
flushSync(() => {
|
|
527
827
|
for (const [key, value] of Object.entries(statePatch)) {
|
|
528
|
-
this.state[key] =
|
|
828
|
+
this.state[key as keyof ModalState] =
|
|
829
|
+
value as ModalState[keyof ModalState];
|
|
529
830
|
}
|
|
530
831
|
});
|
|
531
832
|
|
|
@@ -547,7 +848,7 @@ export class Modal extends Component {
|
|
|
547
848
|
return this;
|
|
548
849
|
}
|
|
549
850
|
|
|
550
|
-
applyStyle(element, style) {
|
|
851
|
+
applyStyle(element: HTMLElement, style: ModalStyle): void {
|
|
551
852
|
element.removeAttribute('style');
|
|
552
853
|
if (!style) {
|
|
553
854
|
this.cache.baseStyle = '';
|
|
@@ -558,27 +859,17 @@ export class Modal extends Component {
|
|
|
558
859
|
this.cache.baseStyle = element.getAttribute('style') || '';
|
|
559
860
|
return;
|
|
560
861
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
});
|
|
569
|
-
}
|
|
862
|
+
Object.entries(style).forEach(([key, value]) => {
|
|
863
|
+
if (value == null) return;
|
|
864
|
+
const name = key.startsWith('--')
|
|
865
|
+
? key
|
|
866
|
+
: key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
867
|
+
element.style.setProperty(name, String(value));
|
|
868
|
+
});
|
|
570
869
|
this.cache.baseStyle = element.getAttribute('style') || '';
|
|
571
870
|
}
|
|
572
871
|
|
|
573
|
-
|
|
574
|
-
// if (!(element instanceof Element)) return;
|
|
575
|
-
// const iconElement = q('.modal-close', element);
|
|
576
|
-
// if (iconElement) {
|
|
577
|
-
// iconElement.style.transform = `translateX(${iconElement.clientHeight / 3}px)`;
|
|
578
|
-
// }
|
|
579
|
-
// }
|
|
580
|
-
|
|
581
|
-
bindReactiveVisibility() {
|
|
872
|
+
bindReactiveVisibility(): void {
|
|
582
873
|
this.cleanup.visibility = createRoot((dispose) => {
|
|
583
874
|
createEffect(() => {
|
|
584
875
|
const visible = !!this.state.visible;
|
|
@@ -588,7 +879,7 @@ export class Modal extends Component {
|
|
|
588
879
|
});
|
|
589
880
|
}
|
|
590
881
|
|
|
591
|
-
applyVisibility(visible) {
|
|
882
|
+
applyVisibility(visible: boolean): void {
|
|
592
883
|
if (visible === this.runtime.visibleApplied) return;
|
|
593
884
|
if (visible) {
|
|
594
885
|
this.showFromState();
|
|
@@ -597,38 +888,39 @@ export class Modal extends Component {
|
|
|
597
888
|
this.hideFromState();
|
|
598
889
|
}
|
|
599
890
|
|
|
600
|
-
showFromState() {
|
|
891
|
+
showFromState(): void {
|
|
601
892
|
if (this.runtime.destroyed) {
|
|
602
893
|
throw new Error('Modal: The current instance has been destroyed.');
|
|
603
894
|
}
|
|
604
895
|
|
|
605
|
-
requireRenderDOM('Modal.show');
|
|
606
|
-
|
|
607
896
|
if (!this.root) this.buildRoot();
|
|
608
897
|
|
|
609
898
|
this.cancelHideTimer();
|
|
610
899
|
this.resetAnimationStyles();
|
|
611
900
|
|
|
612
901
|
const { onShow, onShown } = this.state;
|
|
613
|
-
|
|
902
|
+
void onShow?.(this);
|
|
614
903
|
|
|
615
|
-
this.cache.previousActiveElement =
|
|
616
|
-
|
|
904
|
+
this.cache.previousActiveElement =
|
|
905
|
+
document.activeElement instanceof HTMLElement
|
|
906
|
+
? document.activeElement
|
|
907
|
+
: null;
|
|
908
|
+
if (this.root && !this.root.parentNode)
|
|
909
|
+
document.body.appendChild(this.root);
|
|
617
910
|
this.lockScroll();
|
|
618
911
|
this.runtime.visibleApplied = true;
|
|
619
912
|
|
|
620
|
-
// this.normalizeIcon(this.dom.modal);
|
|
621
913
|
this.bindEvents(this.root);
|
|
622
914
|
this.focusFirst();
|
|
623
915
|
|
|
624
|
-
|
|
916
|
+
void onShown?.(this);
|
|
625
917
|
}
|
|
626
918
|
|
|
627
|
-
hideFromState() {
|
|
919
|
+
hideFromState(): void {
|
|
628
920
|
if (!this.runtime.visibleApplied || !this.root) return;
|
|
629
921
|
|
|
630
922
|
const { onHide, onHidden } = this.state;
|
|
631
|
-
|
|
923
|
+
void onHide?.(this);
|
|
632
924
|
|
|
633
925
|
this.runtime.visibleApplied = false;
|
|
634
926
|
this.clearEvents();
|
|
@@ -652,14 +944,14 @@ export class Modal extends Component {
|
|
|
652
944
|
);
|
|
653
945
|
}
|
|
654
946
|
|
|
655
|
-
bindEvents(root) {
|
|
947
|
+
bindEvents(root: Element | null): void {
|
|
656
948
|
this.clearEvents();
|
|
657
949
|
this.bindOverlayCloseEvent(root);
|
|
658
950
|
this.bindDocumentKeyEvent();
|
|
659
951
|
this.bindInsideEvent();
|
|
660
952
|
}
|
|
661
953
|
|
|
662
|
-
bindOverlayCloseEvent(root) {
|
|
954
|
+
bindOverlayCloseEvent(root: Element | null): void {
|
|
663
955
|
if (!root) return;
|
|
664
956
|
this.cleanup.events.on('bg', root, 'click', (event) => {
|
|
665
957
|
if (this.state.bgClose && event.target === root) {
|
|
@@ -668,9 +960,9 @@ export class Modal extends Component {
|
|
|
668
960
|
});
|
|
669
961
|
}
|
|
670
962
|
|
|
671
|
-
bindDocumentKeyEvent() {
|
|
963
|
+
bindDocumentKeyEvent(): void {
|
|
672
964
|
this.cleanup.events.on('keydown', document, 'keydown', (event) => {
|
|
673
|
-
if (!this.state.visible) return;
|
|
965
|
+
if (!this.state.visible || !(event instanceof KeyboardEvent)) return;
|
|
674
966
|
|
|
675
967
|
if (event.key === 'Escape' && this.state.escClose) {
|
|
676
968
|
event.preventDefault();
|
|
@@ -682,20 +974,16 @@ export class Modal extends Component {
|
|
|
682
974
|
});
|
|
683
975
|
}
|
|
684
976
|
|
|
685
|
-
bindInsideEvent() {
|
|
977
|
+
bindInsideEvent(): void {
|
|
686
978
|
if (!this.dom.modal) return;
|
|
687
979
|
|
|
688
980
|
this.cleanup.events.on('inside', this.dom.modal, 'click', (event) => {
|
|
689
981
|
const target = event.target instanceof Element ? event.target : null;
|
|
690
|
-
const actionEl = target?.closest('[data-action]');
|
|
691
|
-
if (!actionEl || !this.dom.modal
|
|
982
|
+
const actionEl = target?.closest<HTMLElement>('[data-action]');
|
|
983
|
+
if (!actionEl || !this.dom.modal?.contains(actionEl)) return;
|
|
692
984
|
|
|
693
985
|
const action = actionEl.dataset.action;
|
|
694
|
-
if (
|
|
695
|
-
action === 'cancel' ||
|
|
696
|
-
action === 'close' ||
|
|
697
|
-
actionEl.classList.contains('modal-close')
|
|
698
|
-
) {
|
|
986
|
+
if (action === 'cancel' || action === 'close') {
|
|
699
987
|
void this.handleCancel();
|
|
700
988
|
return;
|
|
701
989
|
}
|
|
@@ -717,7 +1005,6 @@ export class Modal extends Component {
|
|
|
717
1005
|
|
|
718
1006
|
if (action === 'confirm') {
|
|
719
1007
|
if (this.isFormMode()) {
|
|
720
|
-
if (actionEl.classList.contains('modal-confirm')) return;
|
|
721
1008
|
this.requestSubmit();
|
|
722
1009
|
return;
|
|
723
1010
|
}
|
|
@@ -726,11 +1013,11 @@ export class Modal extends Component {
|
|
|
726
1013
|
});
|
|
727
1014
|
}
|
|
728
1015
|
|
|
729
|
-
clearEvents() {
|
|
1016
|
+
clearEvents(): void {
|
|
730
1017
|
this.cleanup.events.clear();
|
|
731
1018
|
}
|
|
732
1019
|
|
|
733
|
-
requestSubmit() {
|
|
1020
|
+
requestSubmit(): void {
|
|
734
1021
|
if (!this.dom.form) {
|
|
735
1022
|
void this.handleConfirm();
|
|
736
1023
|
return;
|
|
@@ -739,7 +1026,7 @@ export class Modal extends Component {
|
|
|
739
1026
|
this.dom.form.requestSubmit();
|
|
740
1027
|
}
|
|
741
1028
|
|
|
742
|
-
async handleFormSubmit(formData) {
|
|
1029
|
+
async handleFormSubmit(formData: FormDataRecord): Promise<void> {
|
|
743
1030
|
if (this.isBusy()) return;
|
|
744
1031
|
|
|
745
1032
|
const data = mergeExtraData(formData, this.state.extraData);
|
|
@@ -751,23 +1038,24 @@ export class Modal extends Component {
|
|
|
751
1038
|
await this.handleSubmit(data);
|
|
752
1039
|
}
|
|
753
1040
|
|
|
754
|
-
async handleNext() {
|
|
1041
|
+
async handleNext(): Promise<void> {
|
|
755
1042
|
if (this.isBusy()) return;
|
|
756
1043
|
if (!this.hasFlow()) return;
|
|
757
1044
|
await this.moveFlow('next');
|
|
758
1045
|
}
|
|
759
1046
|
|
|
760
|
-
async handleBack() {
|
|
1047
|
+
async handleBack(): Promise<void> {
|
|
761
1048
|
if (this.isBusy()) return;
|
|
762
1049
|
if (!this.hasFlow()) return;
|
|
763
1050
|
await this.moveFlow('back');
|
|
764
1051
|
}
|
|
765
1052
|
|
|
766
|
-
hasFlow() {
|
|
1053
|
+
hasFlow(): this is this & { state: ModalState & { flow: FlowLike } } {
|
|
767
1054
|
return isFlowLike(this.state.flow) && !!this.state.flow;
|
|
768
1055
|
}
|
|
769
1056
|
|
|
770
|
-
async moveFlow(direction) {
|
|
1057
|
+
async moveFlow(direction: FlowDirection): Promise<void> {
|
|
1058
|
+
if (!this.hasFlow()) return;
|
|
771
1059
|
const payload = this.createFlowPayload();
|
|
772
1060
|
if (payload === false) return;
|
|
773
1061
|
|
|
@@ -776,7 +1064,9 @@ export class Modal extends Component {
|
|
|
776
1064
|
});
|
|
777
1065
|
|
|
778
1066
|
try {
|
|
779
|
-
const snapshot = await
|
|
1067
|
+
const snapshot = await Promise.resolve(
|
|
1068
|
+
this.state.flow[direction](payload)
|
|
1069
|
+
);
|
|
780
1070
|
this.syncFlowView(this.state.flow, snapshot);
|
|
781
1071
|
} catch (error) {
|
|
782
1072
|
console.error(`Modal.flow.${direction} error:`, error);
|
|
@@ -790,7 +1080,7 @@ export class Modal extends Component {
|
|
|
790
1080
|
}
|
|
791
1081
|
}
|
|
792
1082
|
|
|
793
|
-
createFlowPayload() {
|
|
1083
|
+
createFlowPayload(): FormDataRecord | null | false {
|
|
794
1084
|
if (!this.isFormMode()) return null;
|
|
795
1085
|
|
|
796
1086
|
const form = this.dom.form;
|
|
@@ -805,7 +1095,11 @@ export class Modal extends Component {
|
|
|
805
1095
|
return this.state.data;
|
|
806
1096
|
}
|
|
807
1097
|
|
|
808
|
-
resolveFlowModalView(
|
|
1098
|
+
resolveFlowModalView(
|
|
1099
|
+
flow: FlowLike,
|
|
1100
|
+
snapshot: FlowSnapshot | null,
|
|
1101
|
+
step?: FlowStep
|
|
1102
|
+
): ModalFlowView {
|
|
809
1103
|
const source = step?.modal ?? step?.view;
|
|
810
1104
|
if (typeof source === 'function') {
|
|
811
1105
|
const result = source({
|
|
@@ -821,7 +1115,10 @@ export class Modal extends Component {
|
|
|
821
1115
|
return clonePlainObject(source);
|
|
822
1116
|
}
|
|
823
1117
|
|
|
824
|
-
syncFlowView(
|
|
1118
|
+
syncFlowView(
|
|
1119
|
+
flow: FlowLike | null,
|
|
1120
|
+
snapshot: FlowSnapshot | null = null
|
|
1121
|
+
): void {
|
|
825
1122
|
if (!flow || !this.state) return;
|
|
826
1123
|
const state = flow.snapshot() || snapshot;
|
|
827
1124
|
const step =
|
|
@@ -832,12 +1129,12 @@ export class Modal extends Component {
|
|
|
832
1129
|
(!hasOwn(modalView, 'text') || !hasOwn(nextText, 'title')) &&
|
|
833
1130
|
step?.title != null;
|
|
834
1131
|
|
|
835
|
-
const patch = {
|
|
1132
|
+
const patch: ModalPatch = {
|
|
836
1133
|
...(shouldInjectStepTitle
|
|
837
1134
|
? {
|
|
838
1135
|
text: {
|
|
839
1136
|
...nextText,
|
|
840
|
-
title: step.title,
|
|
1137
|
+
title: textValue(step.title),
|
|
841
1138
|
},
|
|
842
1139
|
}
|
|
843
1140
|
: {}),
|
|
@@ -857,7 +1154,7 @@ export class Modal extends Component {
|
|
|
857
1154
|
this.update(patch);
|
|
858
1155
|
}
|
|
859
1156
|
|
|
860
|
-
async handleConfirm() {
|
|
1157
|
+
async handleConfirm(): Promise<void> {
|
|
861
1158
|
if (this.isBusy()) return;
|
|
862
1159
|
|
|
863
1160
|
flushSync(() => {
|
|
@@ -865,7 +1162,7 @@ export class Modal extends Component {
|
|
|
865
1162
|
});
|
|
866
1163
|
|
|
867
1164
|
try {
|
|
868
|
-
|
|
1165
|
+
await Promise.resolve(this.state.onConfirm?.(this));
|
|
869
1166
|
this.hide();
|
|
870
1167
|
} catch (error) {
|
|
871
1168
|
console.error('Modal.onConfirm error:', error);
|
|
@@ -878,7 +1175,7 @@ export class Modal extends Component {
|
|
|
878
1175
|
}
|
|
879
1176
|
}
|
|
880
1177
|
|
|
881
|
-
async handleCancel() {
|
|
1178
|
+
async handleCancel(): Promise<void> {
|
|
882
1179
|
if (this.isBusy()) return;
|
|
883
1180
|
|
|
884
1181
|
flushSync(() => {
|
|
@@ -886,7 +1183,7 @@ export class Modal extends Component {
|
|
|
886
1183
|
});
|
|
887
1184
|
|
|
888
1185
|
try {
|
|
889
|
-
|
|
1186
|
+
await Promise.resolve(this.state.onCancel?.(this));
|
|
890
1187
|
this.hide();
|
|
891
1188
|
} catch (error) {
|
|
892
1189
|
console.error('Modal.onCancel error:', error);
|
|
@@ -899,7 +1196,7 @@ export class Modal extends Component {
|
|
|
899
1196
|
}
|
|
900
1197
|
}
|
|
901
1198
|
|
|
902
|
-
async handleSubmit(data) {
|
|
1199
|
+
async handleSubmit(data: FormDataRecord): Promise<void> {
|
|
903
1200
|
if (!this.state.onSubmit) return;
|
|
904
1201
|
|
|
905
1202
|
flushSync(() => {
|
|
@@ -907,7 +1204,7 @@ export class Modal extends Component {
|
|
|
907
1204
|
});
|
|
908
1205
|
|
|
909
1206
|
try {
|
|
910
|
-
await Promise.resolve(this.state.onSubmit(data));
|
|
1207
|
+
await Promise.resolve(this.state.onSubmit(data, this));
|
|
911
1208
|
this.state.extraData = null;
|
|
912
1209
|
} catch (error) {
|
|
913
1210
|
console.error('Modal.onSubmit error:', error);
|
|
@@ -920,10 +1217,10 @@ export class Modal extends Component {
|
|
|
920
1217
|
}
|
|
921
1218
|
}
|
|
922
1219
|
|
|
923
|
-
trapFocus(event) {
|
|
1220
|
+
trapFocus(event: KeyboardEvent): void {
|
|
924
1221
|
if (!this.dom.modal) return;
|
|
925
1222
|
const focusable = Array.from(
|
|
926
|
-
all(
|
|
1223
|
+
all<HTMLElement>(
|
|
927
1224
|
'a[href], button:not([disabled]):not([data-action=close]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])',
|
|
928
1225
|
this.dom.modal
|
|
929
1226
|
)
|
|
@@ -943,17 +1240,17 @@ export class Modal extends Component {
|
|
|
943
1240
|
|
|
944
1241
|
if (event.shiftKey && document.activeElement === first) {
|
|
945
1242
|
event.preventDefault();
|
|
946
|
-
last
|
|
1243
|
+
last?.focus();
|
|
947
1244
|
} else if (!event.shiftKey && document.activeElement === last) {
|
|
948
1245
|
event.preventDefault();
|
|
949
|
-
first
|
|
1246
|
+
first?.focus();
|
|
950
1247
|
}
|
|
951
1248
|
}
|
|
952
1249
|
|
|
953
|
-
focusFirst() {
|
|
1250
|
+
focusFirst(): void {
|
|
954
1251
|
if (!this.dom.modal) return;
|
|
955
1252
|
const focusRoot = this.dom.form?.root || this.dom.modal;
|
|
956
|
-
const firstFocusable = q(
|
|
1253
|
+
const firstFocusable = q<HTMLElement>(
|
|
957
1254
|
'input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]):not([data-action=close]), [tabindex]:not([tabindex="-1"])',
|
|
958
1255
|
focusRoot
|
|
959
1256
|
);
|
|
@@ -964,8 +1261,8 @@ export class Modal extends Component {
|
|
|
964
1261
|
}
|
|
965
1262
|
}
|
|
966
1263
|
|
|
967
|
-
lockScroll() {
|
|
968
|
-
if (this.runtime.scrollLocked
|
|
1264
|
+
lockScroll(): void {
|
|
1265
|
+
if (this.runtime.scrollLocked) return;
|
|
969
1266
|
if (modalScrollLockCount === 0) {
|
|
970
1267
|
modalBodyOverflow = document.body.style.overflow;
|
|
971
1268
|
document.body.style.overflow = 'hidden';
|
|
@@ -974,8 +1271,8 @@ export class Modal extends Component {
|
|
|
974
1271
|
this.runtime.scrollLocked = true;
|
|
975
1272
|
}
|
|
976
1273
|
|
|
977
|
-
unlockScroll() {
|
|
978
|
-
if (!this.runtime.scrollLocked
|
|
1274
|
+
unlockScroll(): void {
|
|
1275
|
+
if (!this.runtime.scrollLocked) return;
|
|
979
1276
|
modalScrollLockCount = Math.max(0, modalScrollLockCount - 1);
|
|
980
1277
|
if (modalScrollLockCount === 0) {
|
|
981
1278
|
document.body.style.overflow = modalBodyOverflow;
|
|
@@ -984,29 +1281,29 @@ export class Modal extends Component {
|
|
|
984
1281
|
this.runtime.scrollLocked = false;
|
|
985
1282
|
}
|
|
986
1283
|
|
|
987
|
-
cancelHideTimer() {
|
|
1284
|
+
cancelHideTimer(): void {
|
|
988
1285
|
if (!this.cleanup.hideTimer) return;
|
|
989
1286
|
clearTimeout(this.cleanup.hideTimer);
|
|
990
1287
|
this.cleanup.hideTimer = null;
|
|
991
1288
|
}
|
|
992
1289
|
|
|
993
|
-
resetAnimationStyles() {
|
|
1290
|
+
resetAnimationStyles(): void {
|
|
994
1291
|
if (!this.dom.modal) return;
|
|
995
1292
|
if (this.cache.baseStyle)
|
|
996
1293
|
this.dom.modal.setAttribute('style', this.cache.baseStyle);
|
|
997
1294
|
else this.dom.modal.removeAttribute('style');
|
|
998
1295
|
}
|
|
999
1296
|
|
|
1000
|
-
finishHide(onHidden) {
|
|
1297
|
+
finishHide(onHidden: ResolvedModalProps['onHidden']): void {
|
|
1001
1298
|
this.cleanup.hideTimer = null;
|
|
1002
|
-
|
|
1299
|
+
this.root?.remove();
|
|
1003
1300
|
this.resetAnimationStyles();
|
|
1004
1301
|
this.unlockScroll();
|
|
1005
1302
|
this.restoreFocus();
|
|
1006
|
-
|
|
1303
|
+
void onHidden?.(this);
|
|
1007
1304
|
}
|
|
1008
1305
|
|
|
1009
|
-
restoreFocus() {
|
|
1306
|
+
restoreFocus(): void {
|
|
1010
1307
|
const target = this.cache.previousActiveElement;
|
|
1011
1308
|
this.cache.previousActiveElement = null;
|
|
1012
1309
|
if (
|
|
@@ -1018,7 +1315,7 @@ export class Modal extends Component {
|
|
|
1018
1315
|
}
|
|
1019
1316
|
}
|
|
1020
1317
|
|
|
1021
|
-
assertActive(method) {
|
|
1318
|
+
assertActive(method: string): void {
|
|
1022
1319
|
if (this.runtime.destroyed) {
|
|
1023
1320
|
throw new Error(
|
|
1024
1321
|
`Modal.${method}: The current instance has been destroyed.`
|
|
@@ -1026,7 +1323,7 @@ export class Modal extends Component {
|
|
|
1026
1323
|
}
|
|
1027
1324
|
}
|
|
1028
1325
|
|
|
1029
|
-
validateStatePatch(patch) {
|
|
1326
|
+
validateStatePatch(patch: ModalStatePatch): void {
|
|
1030
1327
|
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
|
|
1031
1328
|
throw new Error('Modal.setState: expects a plain object patch.');
|
|
1032
1329
|
}
|
|
@@ -1042,25 +1339,34 @@ export class Modal extends Component {
|
|
|
1042
1339
|
`Modal.setState: "${key}" cannot be updated after initialization.`
|
|
1043
1340
|
);
|
|
1044
1341
|
}
|
|
1045
|
-
validateParam(
|
|
1342
|
+
validateParam(
|
|
1343
|
+
key,
|
|
1344
|
+
patch[key as keyof ModalStatePatch],
|
|
1345
|
+
MODAL_STATE_SCHEMA[key as keyof typeof MODAL_STATE_SCHEMA],
|
|
1346
|
+
'Modal.setState'
|
|
1347
|
+
);
|
|
1046
1348
|
}
|
|
1047
1349
|
}
|
|
1048
1350
|
|
|
1049
|
-
setState(patch = {}) {
|
|
1351
|
+
override setState(patch: ModalStatePatch = {}): this {
|
|
1050
1352
|
this.assertActive('setState');
|
|
1051
1353
|
this.validateStatePatch(patch);
|
|
1052
1354
|
|
|
1053
1355
|
flushSync(() => {
|
|
1054
1356
|
for (const [key, value] of Object.entries(patch)) {
|
|
1055
|
-
if (key === 'fields') this.cache.fieldIds
|
|
1056
|
-
this.state[key] =
|
|
1057
|
-
key === 'fields' && Array.isArray(value)
|
|
1357
|
+
if (key === 'fields') this.cache.fieldIds?.clear();
|
|
1358
|
+
this.state[key as keyof ModalState] =
|
|
1359
|
+
key === 'fields' && Array.isArray(value)
|
|
1360
|
+
? (cloneFields(
|
|
1361
|
+
value as FormField[]
|
|
1362
|
+
) as ModalState[keyof ModalState])
|
|
1363
|
+
: (value as ModalState[keyof ModalState]);
|
|
1058
1364
|
}
|
|
1059
1365
|
});
|
|
1060
1366
|
return this;
|
|
1061
1367
|
}
|
|
1062
1368
|
|
|
1063
|
-
show() {
|
|
1369
|
+
show(): this {
|
|
1064
1370
|
this.assertActive('show');
|
|
1065
1371
|
flushSync(() => {
|
|
1066
1372
|
this.state.visible = true;
|
|
@@ -1068,7 +1374,7 @@ export class Modal extends Component {
|
|
|
1068
1374
|
return this;
|
|
1069
1375
|
}
|
|
1070
1376
|
|
|
1071
|
-
hide() {
|
|
1377
|
+
hide(): this {
|
|
1072
1378
|
this.assertActive('hide');
|
|
1073
1379
|
flushSync(() => {
|
|
1074
1380
|
this.state.visible = false;
|
|
@@ -1076,13 +1382,13 @@ export class Modal extends Component {
|
|
|
1076
1382
|
return this;
|
|
1077
1383
|
}
|
|
1078
1384
|
|
|
1079
|
-
setFields(data, force = false) {
|
|
1385
|
+
setFields(data: readonly FormField[] | null, force = false): this {
|
|
1080
1386
|
validateParam('data', data, MODAL_FIELDS_RULE, 'Modal.setFields');
|
|
1081
1387
|
this.applyProps({ fields: data }, { validate: false, force });
|
|
1082
1388
|
return this;
|
|
1083
1389
|
}
|
|
1084
1390
|
|
|
1085
|
-
addFields(data) {
|
|
1391
|
+
addFields(data: FormDataRecord): this {
|
|
1086
1392
|
validateParam('data', data, MODAL_EXTRA_FIELDS_RULE, 'Modal.addFields');
|
|
1087
1393
|
flushSync(() => {
|
|
1088
1394
|
this.state.extraData = data;
|
|
@@ -1090,7 +1396,7 @@ export class Modal extends Component {
|
|
|
1090
1396
|
return this;
|
|
1091
1397
|
}
|
|
1092
1398
|
|
|
1093
|
-
setContent(content, force = false) {
|
|
1399
|
+
setContent(content: ModalContent, force = false): this {
|
|
1094
1400
|
validateParam('content', content, MODAL_CONTENT_RULE, 'Modal.setContent');
|
|
1095
1401
|
|
|
1096
1402
|
if (this.isFormMode() && !force) {
|
|
@@ -1103,18 +1409,35 @@ export class Modal extends Component {
|
|
|
1103
1409
|
return this;
|
|
1104
1410
|
}
|
|
1105
1411
|
|
|
1106
|
-
update(
|
|
1107
|
-
|
|
1412
|
+
override update(
|
|
1413
|
+
patch?: Partial<ResolvedModalProps> | null,
|
|
1414
|
+
options?: ComponentUpdateOptions
|
|
1415
|
+
): this;
|
|
1416
|
+
update(patch?: ModalPatch | null, force?: boolean): this;
|
|
1417
|
+
update(
|
|
1418
|
+
patch: ModalPatch | Partial<ResolvedModalProps> | null = {},
|
|
1419
|
+
forceOrOptions: boolean | ComponentUpdateOptions = false
|
|
1420
|
+
): this {
|
|
1421
|
+
const force =
|
|
1422
|
+
typeof forceOrOptions === 'boolean'
|
|
1423
|
+
? forceOrOptions
|
|
1424
|
+
: !!forceOrOptions.force;
|
|
1425
|
+
return this.applyProps((patch || {}) as ModalPatch, {
|
|
1426
|
+
validate: true,
|
|
1427
|
+
force,
|
|
1428
|
+
});
|
|
1108
1429
|
}
|
|
1109
1430
|
|
|
1110
|
-
reset() {
|
|
1111
|
-
this.cache.fieldIds
|
|
1431
|
+
reset(): this {
|
|
1432
|
+
this.cache.fieldIds?.clear();
|
|
1433
|
+
if (!this.cache.initial) return this;
|
|
1112
1434
|
|
|
1113
1435
|
const initialProps = cloneProps(this.cache.initial);
|
|
1114
|
-
|
|
1115
|
-
delete
|
|
1116
|
-
|
|
1117
|
-
this.
|
|
1436
|
+
const patch: ModalPatch = { ...(initialProps as unknown as ModalProps) };
|
|
1437
|
+
delete patch.id;
|
|
1438
|
+
delete patch.lazy;
|
|
1439
|
+
this.props = normalizeProps(patch);
|
|
1440
|
+
this.applyProps(patch, { validate: false });
|
|
1118
1441
|
flushSync(() => {
|
|
1119
1442
|
this.state.data = null;
|
|
1120
1443
|
this.state.extraData = null;
|
|
@@ -1122,11 +1445,11 @@ export class Modal extends Component {
|
|
|
1122
1445
|
return this;
|
|
1123
1446
|
}
|
|
1124
1447
|
|
|
1125
|
-
resetContent() {
|
|
1448
|
+
resetContent(): this {
|
|
1126
1449
|
return this.setContent(this.cache.initial?.content ?? '');
|
|
1127
1450
|
}
|
|
1128
1451
|
|
|
1129
|
-
resetFields() {
|
|
1452
|
+
resetFields(): this {
|
|
1130
1453
|
return this.setFields(
|
|
1131
1454
|
Array.isArray(this.cache.initial?.fields)
|
|
1132
1455
|
? cloneFields(this.cache.initial.fields)
|
|
@@ -1134,12 +1457,12 @@ export class Modal extends Component {
|
|
|
1134
1457
|
);
|
|
1135
1458
|
}
|
|
1136
1459
|
|
|
1137
|
-
onDestroy() {
|
|
1460
|
+
protected override onDestroy(): void {
|
|
1138
1461
|
const wasVisible = !!this.runtime.visibleApplied;
|
|
1139
1462
|
const onHide = this.state?.onHide;
|
|
1140
1463
|
const onHidden = this.state?.onHidden;
|
|
1141
1464
|
|
|
1142
|
-
if (wasVisible
|
|
1465
|
+
if (wasVisible) void onHide?.(this);
|
|
1143
1466
|
|
|
1144
1467
|
this.cancelHideTimer();
|
|
1145
1468
|
this.clearEvents();
|
|
@@ -1150,8 +1473,8 @@ export class Modal extends Component {
|
|
|
1150
1473
|
this.cleanup.view?.();
|
|
1151
1474
|
this.cleanup.view = null;
|
|
1152
1475
|
|
|
1153
|
-
|
|
1154
|
-
if (wasVisible
|
|
1476
|
+
this.root?.remove();
|
|
1477
|
+
if (wasVisible) void onHidden?.(this);
|
|
1155
1478
|
|
|
1156
1479
|
this.cache = {
|
|
1157
1480
|
initial: null,
|
|
@@ -1162,13 +1485,13 @@ export class Modal extends Component {
|
|
|
1162
1485
|
};
|
|
1163
1486
|
}
|
|
1164
1487
|
|
|
1165
|
-
destroy() {
|
|
1166
|
-
if (this.runtime.destroyed) return;
|
|
1488
|
+
override destroy(): this {
|
|
1489
|
+
if (this.runtime.destroyed) return this;
|
|
1167
1490
|
super.destroy();
|
|
1168
1491
|
return this;
|
|
1169
1492
|
}
|
|
1170
1493
|
}
|
|
1171
1494
|
|
|
1172
|
-
export function createModal(input) {
|
|
1495
|
+
export function createModal(input: ModalProps = {}): Modal {
|
|
1173
1496
|
return new Modal(input);
|
|
1174
1497
|
}
|