@design.estate/dees-catalog 3.70.0 → 3.71.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_bundle/bundle.js +405 -154
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-layout/dees-stepper/dees-stepper.d.ts +37 -0
- package/dist_ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.js +92 -91
- package/dist_ts_web/elements/00group-layout/dees-stepper/dees-stepper.js +348 -65
- package/dist_ts_web/elements/00group-overlay/dees-modal/dees-modal.js +2 -4
- package/dist_watch/bundle.js +484 -154
- package/dist_watch/bundle.js.map +3 -3
- package/package.json +1 -1
- package/readme.plan.md +352 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts +133 -130
- package/ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts +345 -65
- package/ts_web/elements/00group-overlay/dees-modal/dees-modal.ts +1 -3
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as plugins from '../../00plugins.js';
|
|
2
|
-
import * as colors from '../../00colors.js';
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
4
|
DeesElement,
|
|
@@ -16,10 +15,16 @@ import {
|
|
|
16
15
|
import * as domtools from '@design.estate/dees-domtools';
|
|
17
16
|
import { stepperDemo } from './dees-stepper.demo.js';
|
|
18
17
|
import { themeDefaultStyles } from '../../00theme.js';
|
|
18
|
+
import { cssGeistFontFamily } from '../../00fonts.js';
|
|
19
|
+
import { zIndexRegistry } from '../../00zindex.js';
|
|
20
|
+
import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
|
|
21
|
+
import type { DeesForm } from '../../00group-form/dees-form/dees-form.js';
|
|
22
|
+
import '../dees-tile/dees-tile.js';
|
|
19
23
|
|
|
20
24
|
export interface IStep {
|
|
21
25
|
title: string;
|
|
22
26
|
content: TemplateResult;
|
|
27
|
+
menuOptions?: plugins.tsclass.website.IMenuItem<DeesStepper>[];
|
|
23
28
|
validationFunc?: (stepper: DeesStepper, htmlElement: HTMLElement, signal?: AbortSignal) => Promise<any>;
|
|
24
29
|
onReturnToStepFunc?: (stepper: DeesStepper, htmlElement: HTMLElement) => Promise<any>;
|
|
25
30
|
validationFuncCalled?: boolean;
|
|
@@ -34,9 +39,32 @@ declare global {
|
|
|
34
39
|
|
|
35
40
|
@customElement('dees-stepper')
|
|
36
41
|
export class DeesStepper extends DeesElement {
|
|
42
|
+
// STATIC
|
|
37
43
|
public static demo = stepperDemo;
|
|
38
44
|
public static demoGroups = ['Layout', 'Form'];
|
|
39
45
|
|
|
46
|
+
public static async createAndShow(optionsArg: {
|
|
47
|
+
steps: IStep[];
|
|
48
|
+
}): Promise<DeesStepper> {
|
|
49
|
+
const body = document.body;
|
|
50
|
+
const stepper = new DeesStepper();
|
|
51
|
+
stepper.steps = optionsArg.steps;
|
|
52
|
+
stepper.overlay = true;
|
|
53
|
+
stepper.windowLayer = await DeesWindowLayer.createAndShow({ blur: true });
|
|
54
|
+
stepper.windowLayer.addEventListener('click', async () => {
|
|
55
|
+
await stepper.destroy();
|
|
56
|
+
});
|
|
57
|
+
body.append(stepper.windowLayer);
|
|
58
|
+
body.append(stepper);
|
|
59
|
+
|
|
60
|
+
// Get z-index for stepper (should be above window layer)
|
|
61
|
+
stepper.stepperZIndex = zIndexRegistry.getNextZIndex();
|
|
62
|
+
zIndexRegistry.register(stepper, stepper.stepperZIndex);
|
|
63
|
+
|
|
64
|
+
return stepper;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// INSTANCE
|
|
40
68
|
@property({
|
|
41
69
|
type: Array,
|
|
42
70
|
})
|
|
@@ -47,6 +75,25 @@ export class DeesStepper extends DeesElement {
|
|
|
47
75
|
})
|
|
48
76
|
accessor selectedStep!: IStep;
|
|
49
77
|
|
|
78
|
+
@property({
|
|
79
|
+
type: Boolean,
|
|
80
|
+
reflect: true,
|
|
81
|
+
})
|
|
82
|
+
accessor overlay: boolean = false;
|
|
83
|
+
|
|
84
|
+
@property({ type: Number, attribute: false })
|
|
85
|
+
accessor stepperZIndex: number = 1000;
|
|
86
|
+
|
|
87
|
+
@property({ type: Object, attribute: false })
|
|
88
|
+
accessor activeForm: DeesForm | null = null;
|
|
89
|
+
|
|
90
|
+
@property({ type: Boolean, attribute: false })
|
|
91
|
+
accessor activeFormValid: boolean = true;
|
|
92
|
+
|
|
93
|
+
private activeFormSubscription?: { unsubscribe: () => void };
|
|
94
|
+
|
|
95
|
+
private windowLayer?: DeesWindowLayer;
|
|
96
|
+
|
|
50
97
|
constructor() {
|
|
51
98
|
super();
|
|
52
99
|
}
|
|
@@ -60,7 +107,24 @@ export class DeesStepper extends DeesElement {
|
|
|
60
107
|
position: absolute;
|
|
61
108
|
width: 100%;
|
|
62
109
|
height: 100%;
|
|
110
|
+
font-family: ${cssGeistFontFamily};
|
|
111
|
+
color: var(--dees-color-text-primary);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* In overlay mode the host is "transparent" to layout — the inner
|
|
116
|
+
* .stepperContainer.overlay is what pins to the viewport and carries the
|
|
117
|
+
* z-index. Keeping :host unpositioned avoids nesting the stacking context
|
|
118
|
+
* under an auto-z-index parent (which was trapping .stepperContainer
|
|
119
|
+
* below DeesWindowLayer's sibling layers). This mirrors how dees-modal
|
|
120
|
+
* keeps its own :host unpositioned and lets .modalContainer drive layout.
|
|
121
|
+
*/
|
|
122
|
+
:host([overlay]) {
|
|
123
|
+
position: static;
|
|
124
|
+
width: 0;
|
|
125
|
+
height: 0;
|
|
63
126
|
}
|
|
127
|
+
|
|
64
128
|
.stepperContainer {
|
|
65
129
|
position: absolute;
|
|
66
130
|
width: 100%;
|
|
@@ -68,101 +132,120 @@ export class DeesStepper extends DeesElement {
|
|
|
68
132
|
overflow: hidden;
|
|
69
133
|
}
|
|
70
134
|
|
|
71
|
-
.
|
|
135
|
+
.stepperContainer.overlay {
|
|
136
|
+
position: fixed;
|
|
137
|
+
top: 0;
|
|
138
|
+
left: 0;
|
|
139
|
+
width: 100vw;
|
|
140
|
+
height: 100vh;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.stepperContainer.predestroy {
|
|
144
|
+
opacity: 0;
|
|
145
|
+
transition: opacity 0.2s ease-in;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
dees-tile.step {
|
|
72
149
|
position: relative;
|
|
73
150
|
pointer-events: none;
|
|
74
|
-
overflow: hidden;
|
|
75
|
-
transition: transform 0.7s cubic-bezier(0.87, 0, 0.13, 1), box-shadow 0.7s cubic-bezier(0.87, 0, 0.13, 1), filter 0.7s cubic-bezier(0.87, 0, 0.13, 1), border 0.7s cubic-bezier(0.87, 0, 0.13, 1);
|
|
76
151
|
max-width: 500px;
|
|
77
152
|
min-height: 300px;
|
|
78
|
-
border-radius: 12px;
|
|
79
|
-
background: ${cssManager.bdTheme('#ffffff', '#0f0f11')};
|
|
80
|
-
border: 1px solid ${cssManager.bdTheme('#e2e8f0', '#272729')};
|
|
81
|
-
color: ${cssManager.bdTheme('#0f172a', '#f5f5f5')};
|
|
82
153
|
margin: auto;
|
|
83
154
|
margin-bottom: 20px;
|
|
84
155
|
filter: opacity(0.55) saturate(0.85);
|
|
85
|
-
|
|
156
|
+
transition: transform 0.7s cubic-bezier(0.87, 0, 0.13, 1), filter 0.7s cubic-bezier(0.87, 0, 0.13, 1);
|
|
86
157
|
user-select: none;
|
|
87
158
|
}
|
|
88
159
|
|
|
89
|
-
.step.selected {
|
|
160
|
+
dees-tile.step.selected {
|
|
90
161
|
pointer-events: all;
|
|
91
162
|
filter: opacity(1) saturate(1);
|
|
92
163
|
user-select: auto;
|
|
93
164
|
}
|
|
94
165
|
|
|
95
|
-
.step.hiddenStep {
|
|
166
|
+
dees-tile.step.hiddenStep {
|
|
96
167
|
filter: opacity(0);
|
|
97
168
|
}
|
|
98
169
|
|
|
99
|
-
.step.entrance {
|
|
100
|
-
transition: transform 0.35s ease,
|
|
170
|
+
dees-tile.step.entrance {
|
|
171
|
+
transition: transform 0.35s ease, filter 0.35s ease;
|
|
101
172
|
}
|
|
102
173
|
|
|
103
|
-
.step.entrance.hiddenStep {
|
|
174
|
+
dees-tile.step.entrance.hiddenStep {
|
|
104
175
|
transform: translateY(16px);
|
|
105
176
|
}
|
|
106
177
|
|
|
107
|
-
.step:last-child {
|
|
178
|
+
dees-tile.step:last-child {
|
|
108
179
|
margin-bottom: 100vh;
|
|
109
180
|
}
|
|
110
181
|
|
|
111
|
-
.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
padding: 6px 14px;
|
|
117
|
-
font-size: 12px;
|
|
118
|
-
border-radius: 999px;
|
|
119
|
-
background: ${cssManager.bdTheme('rgba(226, 232, 240, 0.5)', 'rgba(63, 63, 70, 0.45)')};
|
|
120
|
-
border: 1px solid ${cssManager.bdTheme('rgba(226, 232, 240, 0.7)', 'rgba(63, 63, 70, 0.6)')};
|
|
182
|
+
.stepperContainer.overlay dees-tile.step::part(outer) {
|
|
183
|
+
box-shadow:
|
|
184
|
+
0 0 0 1px ${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 100% / 0.03)')},
|
|
185
|
+
0 8px 40px ${cssManager.bdTheme('hsl(0 0% 0% / 0.12)', 'hsl(0 0% 0% / 0.5)')},
|
|
186
|
+
0 2px 8px ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 0% / 0.25)')};
|
|
121
187
|
}
|
|
122
188
|
|
|
123
|
-
.step
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
189
|
+
.step-header {
|
|
190
|
+
height: 36px;
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
justify-content: space-between;
|
|
194
|
+
padding: 0 8px 0 12px;
|
|
195
|
+
gap: 12px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.goBack-spacer {
|
|
199
|
+
width: 1px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.step-header .goBack {
|
|
127
203
|
display: inline-flex;
|
|
128
204
|
align-items: center;
|
|
129
205
|
gap: 6px;
|
|
130
|
-
|
|
206
|
+
height: 24px;
|
|
207
|
+
padding: 0 8px;
|
|
131
208
|
font-size: 12px;
|
|
132
209
|
font-weight: 500;
|
|
133
|
-
|
|
134
|
-
border:
|
|
135
|
-
background:
|
|
136
|
-
color:
|
|
210
|
+
line-height: 1;
|
|
211
|
+
border: none;
|
|
212
|
+
background: transparent;
|
|
213
|
+
color: var(--dees-color-text-muted);
|
|
214
|
+
border-radius: 4px;
|
|
137
215
|
cursor: pointer;
|
|
138
|
-
transition:
|
|
216
|
+
transition: background 0.15s ease, color 0.15s ease, transform 0.2s ease;
|
|
139
217
|
}
|
|
140
218
|
|
|
141
|
-
.step .goBack:hover {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
background: ${cssManager.bdTheme('rgba(226, 232, 240, 0.95)', 'rgba(63, 63, 70, 0.7)')};
|
|
219
|
+
.step-header .goBack:hover {
|
|
220
|
+
background: var(--dees-color-hover);
|
|
221
|
+
color: var(--dees-color-text-secondary);
|
|
145
222
|
transform: translateX(-2px);
|
|
146
223
|
}
|
|
147
224
|
|
|
148
|
-
.step .goBack:active {
|
|
149
|
-
|
|
150
|
-
border-color: ${cssManager.bdTheme(colors.dark.blueActive, colors.dark.blueActive)};
|
|
151
|
-
background: ${cssManager.bdTheme('rgba(226, 232, 240, 0.85)', 'rgba(63, 63, 70, 0.6)')};
|
|
225
|
+
.step-header .goBack:active {
|
|
226
|
+
background: ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 15%)')};
|
|
152
227
|
}
|
|
153
228
|
|
|
154
|
-
.step .goBack span {
|
|
229
|
+
.step-header .goBack span {
|
|
155
230
|
transition: transform 0.2s ease;
|
|
156
231
|
display: inline-block;
|
|
157
232
|
}
|
|
158
233
|
|
|
159
|
-
.step .goBack:hover span {
|
|
234
|
+
.step-header .goBack:hover span {
|
|
160
235
|
transform: translateX(-2px);
|
|
161
236
|
}
|
|
162
237
|
|
|
163
|
-
.step .
|
|
238
|
+
.step-header .stepCounter {
|
|
239
|
+
color: var(--dees-color-text-muted);
|
|
240
|
+
font-size: 12px;
|
|
241
|
+
font-weight: 500;
|
|
242
|
+
letter-spacing: -0.01em;
|
|
243
|
+
padding: 0 8px;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.step-body .title {
|
|
164
247
|
text-align: center;
|
|
165
|
-
padding-top:
|
|
248
|
+
padding-top: 32px;
|
|
166
249
|
font-family: 'Geist Sans', sans-serif;
|
|
167
250
|
font-size: 24px;
|
|
168
251
|
font-weight: 600;
|
|
@@ -170,35 +253,142 @@ export class DeesStepper extends DeesElement {
|
|
|
170
253
|
color: inherit;
|
|
171
254
|
}
|
|
172
255
|
|
|
173
|
-
.step .content {
|
|
256
|
+
.step-body .content {
|
|
174
257
|
padding: 32px;
|
|
175
258
|
}
|
|
259
|
+
|
|
260
|
+
/* --- Footer: modal-style bottom buttons --- */
|
|
261
|
+
.bottomButtons {
|
|
262
|
+
display: flex;
|
|
263
|
+
flex-direction: row;
|
|
264
|
+
justify-content: flex-end;
|
|
265
|
+
align-items: center;
|
|
266
|
+
gap: 0;
|
|
267
|
+
height: 36px;
|
|
268
|
+
width: 100%;
|
|
269
|
+
box-sizing: border-box;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.bottomButtons .bottomButton {
|
|
273
|
+
padding: 0 16px;
|
|
274
|
+
height: 100%;
|
|
275
|
+
text-align: center;
|
|
276
|
+
font-size: 12px;
|
|
277
|
+
font-weight: 500;
|
|
278
|
+
cursor: pointer;
|
|
279
|
+
user-select: none;
|
|
280
|
+
transition: all 0.15s ease;
|
|
281
|
+
background: transparent;
|
|
282
|
+
border: none;
|
|
283
|
+
border-left: 1px solid var(--dees-color-border-subtle);
|
|
284
|
+
color: var(--dees-color-text-muted);
|
|
285
|
+
white-space: nowrap;
|
|
286
|
+
display: flex;
|
|
287
|
+
align-items: center;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.bottomButtons .bottomButton:first-child {
|
|
291
|
+
border-left: none;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.bottomButtons .bottomButton:hover {
|
|
295
|
+
background: var(--dees-color-hover);
|
|
296
|
+
color: var(--dees-color-text-primary);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.bottomButtons .bottomButton:active {
|
|
300
|
+
background: ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(0 0% 13%)')};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.bottomButtons .bottomButton.primary {
|
|
304
|
+
color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
|
305
|
+
font-weight: 600;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.bottomButtons .bottomButton.primary:hover {
|
|
309
|
+
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.08)', 'hsl(213.1 93.9% 67.8% / 0.08)')};
|
|
310
|
+
color: ${cssManager.bdTheme('hsl(217.2 91.2% 50%)', 'hsl(213.1 93.9% 75%)')};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.bottomButtons .bottomButton.primary:active {
|
|
314
|
+
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.12)', 'hsl(213.1 93.9% 67.8% / 0.12)')};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.bottomButtons .bottomButton.disabled {
|
|
318
|
+
pointer-events: none;
|
|
319
|
+
opacity: 0.4;
|
|
320
|
+
cursor: not-allowed;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.bottomButtons .bottomButton.disabled:hover {
|
|
324
|
+
background: transparent;
|
|
325
|
+
color: var(--dees-color-text-muted);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* Hint shown on the left of the footer when the active step's form has
|
|
329
|
+
unfilled required fields. Uses margin-right: auto to push right-aligned
|
|
330
|
+
buttons to the right while keeping the hint flush-left. */
|
|
331
|
+
.bottomButtons .stepHint {
|
|
332
|
+
margin-right: auto;
|
|
333
|
+
padding: 0 16px;
|
|
334
|
+
font-size: 11px;
|
|
335
|
+
line-height: 1;
|
|
336
|
+
letter-spacing: -0.01em;
|
|
337
|
+
color: var(--dees-color-text-muted);
|
|
338
|
+
display: flex;
|
|
339
|
+
align-items: center;
|
|
340
|
+
user-select: none;
|
|
341
|
+
}
|
|
176
342
|
`,
|
|
177
343
|
];
|
|
178
344
|
|
|
179
345
|
public render() {
|
|
180
346
|
return html`
|
|
181
|
-
<div
|
|
182
|
-
${this.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
347
|
+
<div
|
|
348
|
+
class="stepperContainer ${this.overlay ? 'overlay' : ''}"
|
|
349
|
+
style=${this.overlay ? `z-index: ${this.stepperZIndex};` : ''}
|
|
350
|
+
>
|
|
351
|
+
${this.steps.map((stepArg, stepIndex) => {
|
|
352
|
+
const isSelected = stepArg === this.selectedStep;
|
|
353
|
+
const isHidden =
|
|
354
|
+
this.getIndexOfStep(stepArg) > this.getIndexOfStep(this.selectedStep);
|
|
355
|
+
const isFirst = stepIndex === 0;
|
|
356
|
+
return html`<dees-tile
|
|
357
|
+
class="step ${isSelected ? 'selected' : ''} ${isHidden ? 'hiddenStep' : ''} ${isFirst ? 'entrance' : ''}"
|
|
358
|
+
>
|
|
359
|
+
<div slot="header" class="step-header">
|
|
360
|
+
${!isFirst
|
|
361
|
+
? html`<div class="goBack" @click=${this.goBack}>
|
|
362
|
+
<span style="font-family: Inter"><-</span> go to previous step
|
|
363
|
+
</div>`
|
|
364
|
+
: html`<div class="goBack-spacer"></div>`}
|
|
194
365
|
<div class="stepCounter">
|
|
195
|
-
Step ${
|
|
196
|
-
${this.steps.length}
|
|
366
|
+
Step ${stepIndex + 1} of ${this.steps.length}
|
|
197
367
|
</div>
|
|
368
|
+
</div>
|
|
369
|
+
<div class="step-body">
|
|
198
370
|
<div class="title">${stepArg.title}</div>
|
|
199
371
|
<div class="content">${stepArg.content}</div>
|
|
200
|
-
</div>
|
|
201
|
-
|
|
372
|
+
</div>
|
|
373
|
+
${stepArg.menuOptions && stepArg.menuOptions.length > 0
|
|
374
|
+
? html`<div slot="footer" class="bottomButtons">
|
|
375
|
+
${isSelected && this.activeForm !== null && !this.activeFormValid
|
|
376
|
+
? html`<div class="stepHint">Complete form to continue</div>`
|
|
377
|
+
: ''}
|
|
378
|
+
${stepArg.menuOptions.map((actionArg, actionIndex) => {
|
|
379
|
+
const isPrimary = actionIndex === stepArg.menuOptions!.length - 1;
|
|
380
|
+
const isDisabled = isPrimary && this.activeForm !== null && !this.activeFormValid;
|
|
381
|
+
return html`
|
|
382
|
+
<div
|
|
383
|
+
class="bottomButton ${isPrimary ? 'primary' : ''} ${isDisabled ? 'disabled' : ''}"
|
|
384
|
+
@click=${() => this.handleMenuOptionClick(actionArg, isPrimary)}
|
|
385
|
+
>${actionArg.name}</div>
|
|
386
|
+
`;
|
|
387
|
+
})}
|
|
388
|
+
</div>`
|
|
389
|
+
: ''}
|
|
390
|
+
</dees-tile>`;
|
|
391
|
+
})}
|
|
202
392
|
</div>
|
|
203
393
|
`;
|
|
204
394
|
}
|
|
@@ -230,6 +420,7 @@ export class DeesStepper extends DeesElement {
|
|
|
230
420
|
if (!selectedStepElement) {
|
|
231
421
|
return;
|
|
232
422
|
}
|
|
423
|
+
this.scanActiveForm(selectedStepElement);
|
|
233
424
|
if (!stepperContainer.style.paddingTop) {
|
|
234
425
|
stepperContainer.style.paddingTop = `${
|
|
235
426
|
stepperContainer.offsetHeight / 2 - selectedStepElement.offsetHeight / 2
|
|
@@ -296,4 +487,93 @@ export class DeesStepper extends DeesElement {
|
|
|
296
487
|
nextStep.validationFuncCalled = false;
|
|
297
488
|
this.selectedStep = nextStep;
|
|
298
489
|
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Scans the currently selected step for a <dees-form> in its content. When
|
|
493
|
+
* found, subscribes to the form's RxJS changeSubject so the primary
|
|
494
|
+
* menuOption button can auto-enable/disable as required fields are filled.
|
|
495
|
+
*
|
|
496
|
+
* If the form reference is the same as the previous activation (e.g. on a
|
|
497
|
+
* same-step re-render), we just recompute validity without re-subscribing.
|
|
498
|
+
*/
|
|
499
|
+
private scanActiveForm(selectedStepElement: HTMLElement) {
|
|
500
|
+
const form = selectedStepElement.querySelector('dees-form') as DeesForm | null;
|
|
501
|
+
if (form === this.activeForm) {
|
|
502
|
+
this.recomputeFormValid();
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
this.activeFormSubscription?.unsubscribe();
|
|
506
|
+
this.activeFormSubscription = undefined;
|
|
507
|
+
this.activeForm = form;
|
|
508
|
+
if (!form) {
|
|
509
|
+
this.activeFormValid = true;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
// Initial check before subscribing, in case the form's firstUpdated fires
|
|
513
|
+
// synchronously between scan and subscribe.
|
|
514
|
+
this.recomputeFormValid();
|
|
515
|
+
this.activeFormSubscription = form.changeSubject.subscribe(() => {
|
|
516
|
+
this.recomputeFormValid();
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Recomputes activeFormValid by checking every required field in the active
|
|
522
|
+
* form for a non-empty value. Mirrors dees-form.updateRequiredStatus's logic
|
|
523
|
+
* but stores the result on the stepper instead of mutating a submit button.
|
|
524
|
+
*/
|
|
525
|
+
private recomputeFormValid() {
|
|
526
|
+
const form = this.activeForm;
|
|
527
|
+
if (!form) {
|
|
528
|
+
this.activeFormValid = true;
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const fields = form.getFormElements();
|
|
532
|
+
this.activeFormValid = fields.every(
|
|
533
|
+
(field) => !field.required || (field.value !== null && field.value !== undefined && field.value !== ''),
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Click handler for menuOption buttons in the footer. For the primary (last)
|
|
539
|
+
* button, if an active form is present, gates on required-field validity and
|
|
540
|
+
* triggers the form's gatherAndDispatch() before running the action. The
|
|
541
|
+
* action is awaited so any async work (e.g. goNext → scroll animation)
|
|
542
|
+
* completes before the click handler returns.
|
|
543
|
+
*/
|
|
544
|
+
private async handleMenuOptionClick(
|
|
545
|
+
optionArg: plugins.tsclass.website.IMenuItem<DeesStepper>,
|
|
546
|
+
isPrimary: boolean,
|
|
547
|
+
) {
|
|
548
|
+
const form = this.activeForm;
|
|
549
|
+
if (isPrimary && form) {
|
|
550
|
+
if (!this.activeFormValid) return;
|
|
551
|
+
await new Promise<void>((resolve) => {
|
|
552
|
+
form.addEventListener('formData', () => resolve(), { once: true });
|
|
553
|
+
form.gatherAndDispatch();
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
await optionArg.action(this);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
public async destroy() {
|
|
560
|
+
const domtools = await this.domtoolsPromise;
|
|
561
|
+
const container = this.shadowRoot!.querySelector('.stepperContainer');
|
|
562
|
+
container?.classList.add('predestroy');
|
|
563
|
+
await domtools.convenience.smartdelay.delayFor(200);
|
|
564
|
+
if (this.parentElement) {
|
|
565
|
+
this.parentElement.removeChild(this);
|
|
566
|
+
}
|
|
567
|
+
if (this.windowLayer) {
|
|
568
|
+
await this.windowLayer.destroy();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Tear down form subscription to avoid leaks when the overlay closes.
|
|
572
|
+
this.activeFormSubscription?.unsubscribe();
|
|
573
|
+
this.activeFormSubscription = undefined;
|
|
574
|
+
this.activeForm = null;
|
|
575
|
+
|
|
576
|
+
// Unregister from z-index registry
|
|
577
|
+
zIndexRegistry.unregister(this);
|
|
578
|
+
}
|
|
299
579
|
}
|