@konomi-app/ui 5.1.0 → 5.3.2

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.js CHANGED
@@ -35,14 +35,168 @@ var createInitialState = () => ({
35
35
  queues: [],
36
36
  steps: [],
37
37
  timer: null,
38
- title: ""
38
+ title: "",
39
+ formFields: [],
40
+ formValues: {},
41
+ formErrors: {},
42
+ formTouched: {},
43
+ formLayout: {},
44
+ formValidateOnChange: true,
45
+ formValidateOnBlur: true,
46
+ stepFormSteps: [],
47
+ stepFormCurrentIndex: 0,
48
+ stepFormNextText: "\u6B21\u3078",
49
+ stepFormPrevText: "\u623B\u308B",
50
+ stepFormSubmitText: "OK"
39
51
  });
40
52
 
53
+ // src/zod-utils.ts
54
+ function resolveTypeName(field) {
55
+ const raw = field._def.typeName ?? field._def.type;
56
+ if (!raw) return "";
57
+ const v4Map = {
58
+ string: "ZodString",
59
+ number: "ZodNumber",
60
+ boolean: "ZodBoolean",
61
+ enum: "ZodEnum",
62
+ date: "ZodDate",
63
+ optional: "ZodOptional",
64
+ nullable: "ZodNullable",
65
+ default: "ZodDefault",
66
+ object: "ZodObject",
67
+ pipe: "ZodPipe"
68
+ };
69
+ return v4Map[raw] ?? raw;
70
+ }
71
+ function unwrapType(zodType) {
72
+ let inner = zodType;
73
+ let required = true;
74
+ let defaultValue = void 0;
75
+ let description = zodType.description ?? "";
76
+ while (true) {
77
+ const typeName = resolveTypeName(inner);
78
+ if (typeName === "ZodOptional" || typeName === "ZodNullable") {
79
+ required = false;
80
+ inner = inner._def.innerType;
81
+ } else if (typeName === "ZodDefault") {
82
+ const raw = inner._def.defaultValue;
83
+ defaultValue = typeof raw === "function" ? raw() : raw;
84
+ inner = inner._def.innerType;
85
+ } else if (typeName === "ZodEffects") {
86
+ inner = inner._def.schema;
87
+ } else {
88
+ break;
89
+ }
90
+ if (!description && inner.description) {
91
+ description = inner.description;
92
+ }
93
+ }
94
+ return { inner, required, defaultValue, description };
95
+ }
96
+ function extractFieldMeta(key, zodType) {
97
+ const { inner, required, defaultValue, description } = unwrapType(zodType);
98
+ const typeName = resolveTypeName(inner);
99
+ let inputType;
100
+ let options = [];
101
+ let min;
102
+ let max;
103
+ let minLength;
104
+ let maxLength;
105
+ switch (typeName) {
106
+ case "ZodString": {
107
+ inputType = "text";
108
+ if (inner.format === "email") inputType = "email";
109
+ else if (inner.format === "url") inputType = "url";
110
+ if (inner.minLength != null) minLength = inner.minLength;
111
+ if (inner.maxLength != null) maxLength = inner.maxLength;
112
+ const checks = inner._def.checks ?? [];
113
+ for (const check of checks) {
114
+ const kind = check.kind ?? check.def?.check;
115
+ const fmt = check.format ?? check.def?.format;
116
+ if (!fmt && (kind === "email" || fmt === "email")) inputType = "email";
117
+ else if (!fmt && (kind === "url" || fmt === "url")) inputType = "url";
118
+ else if (fmt === "email" && inputType === "text") inputType = "email";
119
+ else if (fmt === "url" && inputType === "text") inputType = "url";
120
+ if (kind === "min" && check.value != null && minLength == null) minLength = check.value;
121
+ if (kind === "max" && check.value != null && maxLength == null) maxLength = check.value;
122
+ }
123
+ break;
124
+ }
125
+ case "ZodNumber": {
126
+ inputType = "number";
127
+ if (inner.minValue != null) min = inner.minValue;
128
+ if (inner.maxValue != null) max = inner.maxValue;
129
+ if (min == null || max == null) {
130
+ const checks = inner._def.checks ?? [];
131
+ for (const check of checks) {
132
+ if (check.kind === "min" && check.value != null && min == null) min = check.value;
133
+ if (check.kind === "max" && check.value != null && max == null) max = check.value;
134
+ }
135
+ }
136
+ break;
137
+ }
138
+ case "ZodBoolean":
139
+ inputType = "checkbox";
140
+ break;
141
+ case "ZodEnum": {
142
+ inputType = "select";
143
+ if (inner.options?.length) {
144
+ options = [...inner.options];
145
+ } else if (inner._def.entries) {
146
+ options = Object.values(inner._def.entries);
147
+ } else if (inner._def.values?.length) {
148
+ options = [...inner._def.values];
149
+ }
150
+ break;
151
+ }
152
+ case "ZodNativeEnum": {
153
+ inputType = "select";
154
+ const enumValues = inner._def.values;
155
+ if (enumValues) {
156
+ options = Object.values(enumValues).filter((v) => typeof v === "string");
157
+ }
158
+ break;
159
+ }
160
+ case "ZodDate":
161
+ inputType = "date";
162
+ break;
163
+ default:
164
+ return null;
165
+ }
166
+ const label = description || key;
167
+ return {
168
+ key,
169
+ inputType,
170
+ label,
171
+ description: description && description !== label ? description : "",
172
+ required,
173
+ options,
174
+ placeholder: "",
175
+ min,
176
+ max,
177
+ minLength,
178
+ maxLength,
179
+ defaultValue
180
+ };
181
+ }
182
+ function extractFormFields(schema) {
183
+ const shapeDef = schema._def.shape;
184
+ if (!shapeDef) return [];
185
+ const shape = typeof shapeDef === "function" ? shapeDef() : shapeDef;
186
+ if (!shape) return [];
187
+ const fields = [];
188
+ for (const [key, zodType] of Object.entries(shape)) {
189
+ const meta = extractFieldMeta(key, zodType);
190
+ if (meta) fields.push(meta);
191
+ }
192
+ return fields;
193
+ }
194
+
41
195
  // src/controller.ts
42
196
  function normalizeItemInput(input) {
43
197
  return typeof input === "string" ? { key: input, label: input } : input;
44
198
  }
45
- var _state, _listeners, _resolver, _timerId, _DialogController_instances, emit_fn, update_fn, createPromise_fn, resolve_fn, clearTimer_fn, updateItemStatus_fn;
199
+ var _state, _listeners, _resolver, _timerId, _formSchema, _formResult, _stepFormSchemas, _stepFormResults, _DialogController_instances, emit_fn, update_fn, validateFormField_fn, updateCurrentStep_fn, validateStepField_fn, createPromise_fn, resolve_fn, clearTimer_fn, updateItemStatus_fn;
46
200
  var DialogController = class {
47
201
  constructor() {
48
202
  __privateAdd(this, _DialogController_instances);
@@ -50,6 +204,10 @@ var DialogController = class {
50
204
  __privateAdd(this, _listeners, /* @__PURE__ */ new Set());
51
205
  __privateAdd(this, _resolver, null);
52
206
  __privateAdd(this, _timerId, null);
207
+ __privateAdd(this, _formSchema, null);
208
+ __privateAdd(this, _formResult, null);
209
+ __privateAdd(this, _stepFormSchemas, []);
210
+ __privateAdd(this, _stepFormResults, {});
53
211
  __privateSet(this, _state, createInitialState());
54
212
  }
55
213
  // ─── Observable ──────────────────────────────────────────
@@ -195,9 +353,202 @@ var DialogController = class {
195
353
  clearSteps() {
196
354
  __privateMethod(this, _DialogController_instances, update_fn).call(this, { steps: [] });
197
355
  }
356
+ // ─── Form ─────────────────────────────────────────────────
357
+ form(schema, options) {
358
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
359
+ __privateSet(this, _formSchema, schema);
360
+ __privateSet(this, _formResult, null);
361
+ const fields = extractFormFields(schema);
362
+ const defaultValues = {};
363
+ for (const field of fields) {
364
+ if (field.defaultValue !== void 0) {
365
+ defaultValues[field.key] = field.defaultValue;
366
+ }
367
+ }
368
+ if (options?.defaultValues) {
369
+ Object.assign(defaultValues, options.defaultValues);
370
+ }
371
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, {
372
+ open: true,
373
+ dialogType: "form",
374
+ title: options?.title ?? "",
375
+ label: "",
376
+ description: options?.description ?? "",
377
+ icon: null,
378
+ showConfirmButton: true,
379
+ showCancelButton: true,
380
+ confirmButtonText: options?.confirmButtonText ?? "OK",
381
+ cancelButtonText: options?.cancelButtonText ?? "\u30AD\u30E3\u30F3\u30BB\u30EB",
382
+ allowOutsideClick: options?.allowOutsideClick ?? false,
383
+ allowEscapeKey: options?.allowEscapeKey ?? true,
384
+ progress: null,
385
+ timer: null,
386
+ formFields: fields,
387
+ formValues: defaultValues,
388
+ formErrors: {},
389
+ formTouched: {},
390
+ formLayout: options?.layout ?? {},
391
+ formValidateOnChange: options?.validateOnChange ?? true,
392
+ formValidateOnBlur: options?.validateOnBlur ?? true
393
+ });
394
+ return __privateMethod(this, _DialogController_instances, createPromise_fn).call(this, null).then((r) => {
395
+ const data = __privateGet(this, _formResult);
396
+ __privateSet(this, _formSchema, null);
397
+ __privateSet(this, _formResult, null);
398
+ return r.isConfirmed ? data : null;
399
+ });
400
+ }
401
+ updateFormField(key, value) {
402
+ const formValues = { ...__privateGet(this, _state).formValues, [key]: value };
403
+ const formTouched = { ...__privateGet(this, _state).formTouched, [key]: true };
404
+ let formErrors = { ...__privateGet(this, _state).formErrors };
405
+ if (__privateGet(this, _formSchema) && __privateGet(this, _state).formValidateOnChange) {
406
+ formErrors = __privateMethod(this, _DialogController_instances, validateFormField_fn).call(this, key, formValues, formErrors);
407
+ }
408
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { formValues, formTouched, formErrors });
409
+ }
410
+ touchFormField(key) {
411
+ const formTouched = { ...__privateGet(this, _state).formTouched, [key]: true };
412
+ let formErrors = { ...__privateGet(this, _state).formErrors };
413
+ if (__privateGet(this, _formSchema) && __privateGet(this, _state).formValidateOnBlur) {
414
+ formErrors = __privateMethod(this, _DialogController_instances, validateFormField_fn).call(this, key, __privateGet(this, _state).formValues, formErrors);
415
+ }
416
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { formTouched, formErrors });
417
+ }
418
+ // ─── Step Form ───────────────────────────────────────────
419
+ showStepForm(steps, options) {
420
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
421
+ __privateSet(this, _stepFormSchemas, steps.map((s) => s.schema ?? null));
422
+ __privateSet(this, _stepFormResults, {});
423
+ const stepFormSteps = steps.map((s) => {
424
+ const fields = s.schema ? extractFormFields(s.schema) : [];
425
+ const values = {};
426
+ for (const f of fields) {
427
+ if (f.defaultValue !== void 0) values[f.key] = f.defaultValue;
428
+ }
429
+ if (s.defaultValues) Object.assign(values, s.defaultValues);
430
+ return {
431
+ key: s.key,
432
+ label: s.label,
433
+ description: s.description ?? "",
434
+ fields,
435
+ values,
436
+ errors: {},
437
+ touched: {},
438
+ layout: s.layout ?? {}
439
+ };
440
+ });
441
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, {
442
+ open: true,
443
+ dialogType: "step-form",
444
+ title: options?.title ?? "",
445
+ label: "",
446
+ description: "",
447
+ icon: null,
448
+ showConfirmButton: false,
449
+ showCancelButton: false,
450
+ allowOutsideClick: options?.allowOutsideClick ?? false,
451
+ allowEscapeKey: options?.allowEscapeKey ?? true,
452
+ progress: null,
453
+ timer: null,
454
+ stepFormSteps,
455
+ stepFormCurrentIndex: 0,
456
+ stepFormNextText: options?.nextButtonText ?? "\u6B21\u3078",
457
+ stepFormPrevText: options?.prevButtonText ?? "\u623B\u308B",
458
+ stepFormSubmitText: options?.submitButtonText ?? "OK",
459
+ cancelButtonText: options?.cancelButtonText ?? "\u30AD\u30E3\u30F3\u30BB\u30EB"
460
+ });
461
+ return __privateMethod(this, _DialogController_instances, createPromise_fn).call(this, null).then((r) => {
462
+ const data = __privateGet(this, _stepFormResults);
463
+ __privateSet(this, _stepFormSchemas, []);
464
+ __privateSet(this, _stepFormResults, {});
465
+ return r.isConfirmed ? data : null;
466
+ });
467
+ }
468
+ onStepNext() {
469
+ const s = __privateGet(this, _state);
470
+ const idx = s.stepFormCurrentIndex;
471
+ const step = s.stepFormSteps[idx];
472
+ if (!step) return;
473
+ const schema = __privateGet(this, _stepFormSchemas)[idx];
474
+ if (schema) {
475
+ const result = schema.safeParse(step.values);
476
+ if (!result.success) {
477
+ const errors = {};
478
+ for (const issue of result.error.issues) {
479
+ const key = issue.path[0]?.toString();
480
+ if (key && !errors[key]) errors[key] = issue.message;
481
+ }
482
+ const touched = {};
483
+ for (const f of step.fields) touched[f.key] = true;
484
+ __privateMethod(this, _DialogController_instances, updateCurrentStep_fn).call(this, { errors, touched });
485
+ return;
486
+ }
487
+ __privateGet(this, _stepFormResults)[step.key] = result.data;
488
+ }
489
+ const isLast = idx === s.stepFormSteps.length - 1;
490
+ if (isLast) {
491
+ const r = { isConfirmed: true, isCanceled: false, isDismissed: false };
492
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { ...createInitialState(), open: false });
493
+ __privateMethod(this, _DialogController_instances, resolve_fn).call(this, r);
494
+ return;
495
+ }
496
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { stepFormCurrentIndex: idx + 1 });
497
+ }
498
+ onStepPrev() {
499
+ const idx = __privateGet(this, _state).stepFormCurrentIndex;
500
+ if (idx <= 0) return;
501
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { stepFormCurrentIndex: idx - 1 });
502
+ }
503
+ updateStepFormField(fieldKey, value) {
504
+ const s = __privateGet(this, _state);
505
+ const idx = s.stepFormCurrentIndex;
506
+ const step = s.stepFormSteps[idx];
507
+ if (!step) return;
508
+ const values = { ...step.values, [fieldKey]: value };
509
+ const touched = { ...step.touched, [fieldKey]: true };
510
+ let errors = { ...step.errors };
511
+ const schema = __privateGet(this, _stepFormSchemas)[idx];
512
+ if (schema && s.formValidateOnChange) {
513
+ errors = __privateMethod(this, _DialogController_instances, validateStepField_fn).call(this, schema, fieldKey, values, errors);
514
+ }
515
+ __privateMethod(this, _DialogController_instances, updateCurrentStep_fn).call(this, { values, touched, errors });
516
+ }
517
+ touchStepFormField(fieldKey) {
518
+ const s = __privateGet(this, _state);
519
+ const idx = s.stepFormCurrentIndex;
520
+ const step = s.stepFormSteps[idx];
521
+ if (!step) return;
522
+ const touched = { ...step.touched, [fieldKey]: true };
523
+ let errors = { ...step.errors };
524
+ const schema = __privateGet(this, _stepFormSchemas)[idx];
525
+ if (schema && s.formValidateOnBlur) {
526
+ errors = __privateMethod(this, _DialogController_instances, validateStepField_fn).call(this, schema, fieldKey, step.values, errors);
527
+ }
528
+ __privateMethod(this, _DialogController_instances, updateCurrentStep_fn).call(this, { touched, errors });
529
+ }
198
530
  // ─── Button actions (called from the component) ──────────
199
531
  onConfirm() {
200
532
  __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
533
+ if (__privateGet(this, _state).dialogType === "form" && __privateGet(this, _formSchema)) {
534
+ const result = __privateGet(this, _formSchema).safeParse(__privateGet(this, _state).formValues);
535
+ if (!result.success) {
536
+ const formErrors = {};
537
+ for (const issue of result.error.issues) {
538
+ const key = issue.path[0]?.toString();
539
+ if (key && !formErrors[key]) {
540
+ formErrors[key] = issue.message;
541
+ }
542
+ }
543
+ const formTouched = {};
544
+ for (const field of __privateGet(this, _state).formFields) {
545
+ formTouched[field.key] = true;
546
+ }
547
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { formErrors, formTouched });
548
+ return;
549
+ }
550
+ __privateSet(this, _formResult, result.data);
551
+ }
201
552
  const r = { isConfirmed: true, isCanceled: false, isDismissed: false };
202
553
  __privateMethod(this, _DialogController_instances, update_fn).call(this, { ...createInitialState(), open: false });
203
554
  __privateMethod(this, _DialogController_instances, resolve_fn).call(this, r);
@@ -221,6 +572,10 @@ _state = new WeakMap();
221
572
  _listeners = new WeakMap();
222
573
  _resolver = new WeakMap();
223
574
  _timerId = new WeakMap();
575
+ _formSchema = new WeakMap();
576
+ _formResult = new WeakMap();
577
+ _stepFormSchemas = new WeakMap();
578
+ _stepFormResults = new WeakMap();
224
579
  _DialogController_instances = new WeakSet();
225
580
  emit_fn = function() {
226
581
  const snapshot = { ...__privateGet(this, _state) };
@@ -230,6 +585,47 @@ update_fn = function(patch) {
230
585
  Object.assign(__privateGet(this, _state), patch);
231
586
  __privateMethod(this, _DialogController_instances, emit_fn).call(this);
232
587
  };
588
+ validateFormField_fn = function(key, values, errors) {
589
+ const result = __privateGet(this, _formSchema).safeParse(values);
590
+ const updated = { ...errors };
591
+ if (result.success) {
592
+ delete updated[key];
593
+ } else {
594
+ const fieldIssue = result.error.issues.find(
595
+ (issue) => issue.path[0]?.toString() === key
596
+ );
597
+ if (fieldIssue) {
598
+ updated[key] = fieldIssue.message;
599
+ } else {
600
+ delete updated[key];
601
+ }
602
+ }
603
+ return updated;
604
+ };
605
+ updateCurrentStep_fn = function(patch) {
606
+ const idx = __privateGet(this, _state).stepFormCurrentIndex;
607
+ const stepFormSteps = __privateGet(this, _state).stepFormSteps.map(
608
+ (st, i) => i === idx ? { ...st, ...patch } : st
609
+ );
610
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { stepFormSteps });
611
+ };
612
+ validateStepField_fn = function(schema, fieldKey, values, errors) {
613
+ const result = schema.safeParse(values);
614
+ const updated = { ...errors };
615
+ if (result.success) {
616
+ delete updated[fieldKey];
617
+ } else {
618
+ const issue = result.error.issues.find(
619
+ (iss) => iss.path[0]?.toString() === fieldKey
620
+ );
621
+ if (issue) {
622
+ updated[fieldKey] = issue.message;
623
+ } else {
624
+ delete updated[fieldKey];
625
+ }
626
+ }
627
+ return updated;
628
+ };
233
629
  // ─── Internal ────────────────────────────────────────────
234
630
  createPromise_fn = function(timer) {
235
631
  return new Promise((resolve) => {
@@ -266,6 +662,7 @@ updateItemStatus_fn = function(key, itemKey, status) {
266
662
  // src/overlay-dialog.ts
267
663
  import { LitElement, html, nothing } from "lit";
268
664
  import { customElement, property, state } from "lit/decorators.js";
665
+ import { keyed } from "lit/directives/keyed.js";
269
666
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
270
667
 
271
668
  // src/styles.ts
@@ -282,7 +679,7 @@ var overlayStyles = css`
282
679
  --dialog-z-index: 1000;
283
680
  --dialog-backdrop-color: rgb(255 255 255 / 0.73);
284
681
  --dialog-backdrop-blur: 4px;
285
- --dialog-transition-duration: 250ms;
682
+ --dialog-transition-duration: 280ms;
286
683
 
287
684
  /* Card */
288
685
  --dialog-card-bg: #fff;
@@ -317,6 +714,24 @@ var overlayStyles = css`
317
714
  --dialog-spinner-track: rgb(59 130 246 / 0.2);
318
715
  --dialog-spinner-arc: var(--dialog-primary);
319
716
 
717
+ /* Form */
718
+ --dialog-form-width: 500px;
719
+ --dialog-form-max-height: 60vh;
720
+ --dialog-form-gap: 16px;
721
+ --dialog-form-columns: 1;
722
+ --dialog-form-label-color: #374151;
723
+ --dialog-form-label-size: 13px;
724
+ --dialog-form-label-weight: 500;
725
+ --dialog-form-input-bg: #fff;
726
+ --dialog-form-input-border: #d1d5db;
727
+ --dialog-form-input-border-focus: var(--dialog-primary);
728
+ --dialog-form-input-radius: 6px;
729
+ --dialog-form-input-padding: 8px 12px;
730
+ --dialog-form-input-font-size: 14px;
731
+ --dialog-form-error-color: var(--dialog-error);
732
+ --dialog-form-hint-color: #9ca3af;
733
+ --dialog-form-required-color: var(--dialog-error);
734
+
320
735
  display: contents;
321
736
  font-family: var(--dialog-font-family);
322
737
  color: var(--dialog-text-color);
@@ -372,7 +787,6 @@ var overlayStyles = css`
372
787
  min-height: var(--dialog-card-min-height);
373
788
  position: relative;
374
789
  overflow: hidden;
375
- transition: all var(--dialog-transition-duration) ease;
376
790
  }
377
791
 
378
792
  @media (min-width: 640px) {
@@ -384,22 +798,99 @@ var overlayStyles = css`
384
798
  }
385
799
  }
386
800
 
801
+ /* ─── Card Open / Close Animations ─── */
802
+
803
+ @keyframes card-enter {
804
+ from {
805
+ opacity: 0;
806
+ transform: translateY(28px) scale(0.96);
807
+ filter: blur(6px);
808
+ }
809
+ to {
810
+ opacity: 1;
811
+ transform: translateY(0) scale(1);
812
+ filter: blur(0);
813
+ }
814
+ }
815
+
816
+ @keyframes card-exit {
817
+ from {
818
+ opacity: 1;
819
+ transform: translateY(0) scale(1);
820
+ filter: blur(0);
821
+ }
822
+ to {
823
+ opacity: 0;
824
+ transform: translateY(14px) scale(0.97);
825
+ filter: blur(4px);
826
+ }
827
+ }
828
+
829
+ .backdrop[data-open] .card {
830
+ animation: card-enter 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
831
+ }
832
+
833
+ .card[data-closing] {
834
+ animation: card-exit 300ms cubic-bezier(0.4, 0, 1, 1) both;
835
+ pointer-events: none;
836
+ }
837
+
387
838
  /* ─── Card Body ─── */
388
839
 
389
840
  .card-body {
390
841
  flex: 1;
391
842
  display: flex;
392
843
  flex-direction: column;
393
- align-items: center;
844
+ align-items: stretch;
394
845
  justify-content: center;
846
+ width: 100%;
847
+ }
848
+
849
+ /* ─── Body Inner (animated content wrapper) ─── */
850
+
851
+ @keyframes body-enter {
852
+ from {
853
+ opacity: 0;
854
+ transform: translateY(10px);
855
+ filter: blur(3px);
856
+ }
857
+ to {
858
+ opacity: 1;
859
+ transform: translateY(0);
860
+ filter: blur(0);
861
+ }
862
+ }
863
+
864
+ .body-inner {
865
+ display: flex;
866
+ flex-direction: column;
867
+ align-items: center;
395
868
  gap: 16px;
396
869
  width: 100%;
870
+ animation: body-enter 380ms 60ms cubic-bezier(0.16, 1, 0.3, 1) both;
397
871
  }
398
872
 
399
873
  /* ─── Spinner ─── */
400
874
 
401
- .spinner {
875
+ @keyframes spinner-enter {
876
+ from {
877
+ opacity: 0;
878
+ transform: scale(0.5);
879
+ }
880
+ to {
881
+ opacity: 1;
882
+ transform: scale(1);
883
+ }
884
+ }
885
+
886
+ .spinner-wrap {
402
887
  font-size: var(--dialog-spinner-size);
888
+ width: 1em;
889
+ height: 1em;
890
+ animation: spinner-enter 450ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
891
+ }
892
+
893
+ .spinner {
403
894
  width: 1em;
404
895
  height: 1em;
405
896
  border-radius: 50%;
@@ -440,6 +931,19 @@ var overlayStyles = css`
440
931
 
441
932
  /* ─── Icon ─── */
442
933
 
934
+ @keyframes icon-enter {
935
+ from {
936
+ opacity: 0;
937
+ transform: scale(0.4) rotate(-12deg);
938
+ filter: blur(4px);
939
+ }
940
+ to {
941
+ opacity: 1;
942
+ transform: scale(1) rotate(0deg);
943
+ filter: blur(0);
944
+ }
945
+ }
946
+
443
947
  .icon-container {
444
948
  width: 64px;
445
949
  height: 64px;
@@ -448,6 +952,7 @@ var overlayStyles = css`
448
952
  align-items: center;
449
953
  justify-content: center;
450
954
  flex-shrink: 0;
955
+ animation: icon-enter 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
451
956
  }
452
957
 
453
958
  .icon-container svg {
@@ -573,6 +1078,7 @@ var overlayStyles = css`
573
1078
  gap: 8px;
574
1079
  width: 100%;
575
1080
  margin-top: 8px;
1081
+ animation: body-enter 320ms 160ms cubic-bezier(0.16, 1, 0.3, 1) both;
576
1082
  }
577
1083
 
578
1084
  @media (min-width: 640px) {
@@ -592,7 +1098,8 @@ var overlayStyles = css`
592
1098
  font-weight: 500;
593
1099
  transition:
594
1100
  background-color 150ms ease,
595
- transform 80ms ease;
1101
+ transform 80ms ease,
1102
+ box-shadow 150ms ease;
596
1103
  min-width: 100px;
597
1104
  text-align: center;
598
1105
  }
@@ -608,6 +1115,7 @@ var overlayStyles = css`
608
1115
 
609
1116
  .btn-confirm:hover {
610
1117
  background-color: var(--dialog-primary-hover);
1118
+ box-shadow: 0 4px 12px rgb(59 130 246 / 0.35);
611
1119
  }
612
1120
 
613
1121
  .btn-cancel {
@@ -631,11 +1139,48 @@ var overlayStyles = css`
631
1139
  list-style: none;
632
1140
  }
633
1141
 
1142
+ @keyframes task-item-enter {
1143
+ from {
1144
+ opacity: 0;
1145
+ transform: translateX(-8px);
1146
+ }
1147
+ to {
1148
+ opacity: 1;
1149
+ transform: translateX(0);
1150
+ }
1151
+ }
1152
+
634
1153
  .task-item {
635
1154
  display: flex;
636
1155
  align-items: center;
637
1156
  gap: 12px;
638
1157
  font-size: 14px;
1158
+ animation: task-item-enter 300ms cubic-bezier(0.16, 1, 0.3, 1) both;
1159
+ }
1160
+
1161
+ .task-item:nth-child(1) {
1162
+ animation-delay: 40ms;
1163
+ }
1164
+ .task-item:nth-child(2) {
1165
+ animation-delay: 80ms;
1166
+ }
1167
+ .task-item:nth-child(3) {
1168
+ animation-delay: 120ms;
1169
+ }
1170
+ .task-item:nth-child(4) {
1171
+ animation-delay: 160ms;
1172
+ }
1173
+ .task-item:nth-child(5) {
1174
+ animation-delay: 200ms;
1175
+ }
1176
+ .task-item:nth-child(6) {
1177
+ animation-delay: 240ms;
1178
+ }
1179
+ .task-item:nth-child(7) {
1180
+ animation-delay: 280ms;
1181
+ }
1182
+ .task-item:nth-child(8) {
1183
+ animation-delay: 320ms;
639
1184
  }
640
1185
 
641
1186
  .task-icon {
@@ -685,6 +1230,7 @@ var overlayStyles = css`
685
1230
 
686
1231
  .task-label {
687
1232
  color: #6b7280;
1233
+ transition: color 250ms ease;
688
1234
  }
689
1235
 
690
1236
  .task-label[data-status='active'] {
@@ -692,8 +1238,21 @@ var overlayStyles = css`
692
1238
  font-weight: 500;
693
1239
  }
694
1240
 
1241
+ @keyframes task-done-flash {
1242
+ 0% {
1243
+ transform: translateX(0);
1244
+ }
1245
+ 30% {
1246
+ transform: translateX(4px);
1247
+ }
1248
+ 100% {
1249
+ transform: translateX(0);
1250
+ }
1251
+ }
1252
+
695
1253
  .task-label[data-status='done'] {
696
1254
  color: var(--dialog-success);
1255
+ animation: task-done-flash 350ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
697
1256
  }
698
1257
 
699
1258
  .task-label[data-status='error'] {
@@ -702,7 +1261,6 @@ var overlayStyles = css`
702
1261
 
703
1262
  .task-label[data-status='skipped'] {
704
1263
  color: #9ca3af;
705
- text-decoration: line-through;
706
1264
  }
707
1265
 
708
1266
  /* ─── Queue ellipsis ─── */
@@ -738,17 +1296,54 @@ var overlayStyles = css`
738
1296
  margin-bottom: 8px;
739
1297
  }
740
1298
 
1299
+ @keyframes step-dot-enter {
1300
+ from {
1301
+ opacity: 0;
1302
+ transform: scale(0.4);
1303
+ }
1304
+ to {
1305
+ opacity: 1;
1306
+ transform: scale(1);
1307
+ }
1308
+ }
1309
+
1310
+ @keyframes step-pulse {
1311
+ 0% {
1312
+ box-shadow:
1313
+ 0 0 0 0 rgb(59 130 246 / 0.5),
1314
+ 0 0 0 3px rgb(59 130 246 / 0.2);
1315
+ }
1316
+ 70% {
1317
+ box-shadow:
1318
+ 0 0 0 7px rgb(59 130 246 / 0),
1319
+ 0 0 0 3px rgb(59 130 246 / 0.2);
1320
+ }
1321
+ 100% {
1322
+ box-shadow:
1323
+ 0 0 0 0 rgb(59 130 246 / 0),
1324
+ 0 0 0 3px rgb(59 130 246 / 0.2);
1325
+ }
1326
+ }
1327
+
741
1328
  .step-dot {
742
1329
  width: 8px;
743
1330
  height: 8px;
744
1331
  border-radius: 50%;
745
1332
  background-color: #d1d5db;
746
- transition: background-color 200ms ease;
1333
+ transition:
1334
+ background-color 250ms ease,
1335
+ transform 250ms cubic-bezier(0.34, 1.56, 0.64, 1),
1336
+ width 250ms cubic-bezier(0.34, 1.56, 0.64, 1);
1337
+ animation: step-dot-enter 400ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
747
1338
  }
748
1339
 
749
1340
  .step-dot[data-status='active'] {
750
1341
  background-color: var(--dialog-primary);
751
- box-shadow: 0 0 0 3px rgb(59 130 246 / 0.2);
1342
+ width: 20px;
1343
+ border-radius: 4px;
1344
+ animation:
1345
+ step-dot-enter 400ms cubic-bezier(0.34, 1.56, 0.64, 1) both,
1346
+ step-pulse 2s 400ms infinite;
752
1347
  }
753
1348
 
754
1349
  .step-dot[data-status='done'] {
@@ -768,6 +1363,205 @@ var overlayStyles = css`
768
1363
  max-width: 24px;
769
1364
  height: 2px;
770
1365
  background-color: #e5e7eb;
1366
+ transition: background-color 400ms ease;
1367
+ }
1368
+
1369
+ /* ─── Form ─── */
1370
+
1371
+ @media (min-width: 640px) {
1372
+ .card[data-type='form'] {
1373
+ width: var(--dialog-form-width);
1374
+ }
1375
+ }
1376
+
1377
+ .form-scroll-container {
1378
+ max-height: var(--dialog-form-max-height);
1379
+ overflow-y: auto;
1380
+ width: 100%;
1381
+ padding: 4px 0;
1382
+ }
1383
+
1384
+ .form-grid {
1385
+ display: grid;
1386
+ grid-template-columns: repeat(var(--dialog-form-columns, 1), 1fr);
1387
+ gap: var(--dialog-form-gap);
1388
+ width: 100%;
1389
+ }
1390
+
1391
+ @media (max-width: 639px) {
1392
+ .form-grid {
1393
+ grid-template-columns: 1fr !important;
1394
+ }
1395
+ }
1396
+
1397
+ .form-field {
1398
+ display: flex;
1399
+ flex-direction: column;
1400
+ gap: 4px;
1401
+ text-align: left;
1402
+ }
1403
+
1404
+ .form-field[data-type='checkbox'] {
1405
+ grid-column: 1 / -1;
1406
+ }
1407
+
1408
+ .form-label {
1409
+ font-size: var(--dialog-form-label-size);
1410
+ font-weight: var(--dialog-form-label-weight);
1411
+ color: var(--dialog-form-label-color);
1412
+ }
1413
+
1414
+ .form-required {
1415
+ color: var(--dialog-form-required-color);
1416
+ margin-left: 2px;
1417
+ }
1418
+
1419
+ .form-input,
1420
+ .form-select {
1421
+ padding: var(--dialog-form-input-padding);
1422
+ font-size: var(--dialog-form-input-font-size);
1423
+ font-family: inherit;
1424
+ background: var(--dialog-form-input-bg);
1425
+ border: 1px solid var(--dialog-form-input-border);
1426
+ border-radius: var(--dialog-form-input-radius);
1427
+ color: var(--dialog-text-color);
1428
+ outline: none;
1429
+ transition:
1430
+ border-color 150ms ease,
1431
+ box-shadow 150ms ease;
1432
+ width: 100%;
1433
+ box-sizing: border-box;
1434
+ }
1435
+
1436
+ .form-input:focus,
1437
+ .form-select:focus {
1438
+ border-color: var(--dialog-form-input-border-focus);
1439
+ box-shadow: 0 0 0 3px rgb(59 130 246 / 0.1);
1440
+ }
1441
+
1442
+ .form-field[data-error] .form-input,
1443
+ .form-field[data-error] .form-select {
1444
+ border-color: var(--dialog-form-error-color);
1445
+ }
1446
+
1447
+ .form-field[data-error] .form-input:focus,
1448
+ .form-field[data-error] .form-select:focus {
1449
+ box-shadow: 0 0 0 3px rgb(239 68 68 / 0.1);
1450
+ }
1451
+
1452
+ .form-error {
1453
+ font-size: 12px;
1454
+ color: var(--dialog-form-error-color);
1455
+ min-height: 1em;
1456
+ }
1457
+
1458
+ .form-hint {
1459
+ font-size: 12px;
1460
+ color: var(--dialog-form-hint-color);
1461
+ }
1462
+
1463
+ .form-checkbox {
1464
+ width: 18px;
1465
+ height: 18px;
1466
+ accent-color: var(--dialog-primary);
1467
+ cursor: pointer;
1468
+ flex-shrink: 0;
1469
+ }
1470
+
1471
+ .form-checkbox-label {
1472
+ display: flex;
1473
+ align-items: center;
1474
+ gap: 8px;
1475
+ cursor: pointer;
1476
+ font-size: var(--dialog-form-input-font-size);
1477
+ color: var(--dialog-form-label-color);
1478
+ }
1479
+
1480
+ .form-checkbox-text {
1481
+ font-size: var(--dialog-form-label-size);
1482
+ font-weight: var(--dialog-form-label-weight);
1483
+ color: var(--dialog-form-label-color);
1484
+ }
1485
+
1486
+ .form-group {
1487
+ border: 1px solid var(--dialog-card-border);
1488
+ border-radius: var(--dialog-form-input-radius);
1489
+ padding: 16px;
1490
+ margin: 0 0 8px;
1491
+ width: 100%;
1492
+ box-sizing: border-box;
1493
+ }
1494
+
1495
+ .form-group-label {
1496
+ font-size: var(--dialog-form-label-size);
1497
+ font-weight: 600;
1498
+ color: var(--dialog-form-label-color);
1499
+ padding: 0 4px;
1500
+ }
1501
+
1502
+ /* ─── Step Form ─── */
1503
+
1504
+ @media (min-width: 640px) {
1505
+ .card[data-type='step-form'] {
1506
+ width: var(--dialog-form-width);
1507
+ }
1508
+ }
1509
+
1510
+ .step-form-counter {
1511
+ font-size: 12px;
1512
+ color: var(--dialog-form-hint-color);
1513
+ text-align: center;
1514
+ margin: 0 0 4px;
1515
+ }
1516
+
1517
+ .actions-step-form {
1518
+ display: flex;
1519
+ flex-direction: row;
1520
+ justify-content: space-between;
1521
+ align-items: center;
1522
+ gap: 8px;
1523
+ margin-top: 20px;
1524
+ width: 100%;
1525
+ }
1526
+
1527
+ .step-form-nav {
1528
+ display: flex;
1529
+ flex-direction: row;
1530
+ gap: 8px;
1531
+ align-items: center;
1532
+ }
1533
+
1534
+ .btn-prev {
1535
+ padding: var(--dialog-btn-padding);
1536
+ font-size: var(--dialog-btn-font-size);
1537
+ font-family: inherit;
1538
+ font-weight: 500;
1539
+ border-radius: var(--dialog-btn-radius);
1540
+ cursor: pointer;
1541
+ border: 1px solid var(--dialog-form-input-border);
1542
+ background: transparent;
1543
+ color: var(--dialog-text-color);
1544
+ transition: background-color 120ms ease, border-color 120ms ease;
1545
+ }
1546
+
1547
+ .btn-prev:hover {
1548
+ background: #f9fafb;
1549
+ border-color: #9ca3af;
1550
+ }
1551
+
1552
+ .btn-prev:active {
1553
+ transform: scale(0.98);
1554
+ }
1555
+
1556
+ @media (max-width: 639px) {
1557
+ .actions-step-form {
1558
+ flex-direction: column-reverse;
1559
+ }
1560
+
1561
+ .step-form-nav {
1562
+ width: 100%;
1563
+ justify-content: flex-end;
1564
+ }
771
1565
  }
772
1566
  `;
773
1567
 
@@ -776,6 +1570,8 @@ var OverlayDialog = class extends LitElement {
776
1570
  constructor() {
777
1571
  super(...arguments);
778
1572
  this._state = createInitialState();
1573
+ this._bodyKey = 0;
1574
+ this._isClosing = false;
779
1575
  this._beforeUnloadHandler = (e) => e.preventDefault();
780
1576
  this._onKeyDown = (e) => {
781
1577
  if (e.key === "Escape" && this._state.open) {
@@ -789,13 +1585,27 @@ var OverlayDialog = class extends LitElement {
789
1585
  this._state = { ...this.controller.state };
790
1586
  this._unsubscribe = this.controller.subscribe((s) => {
791
1587
  const wasOpen = this._state.open;
792
- this._state = s;
793
- this._syncBodyScroll(s.open);
1588
+ const prevDialogType = this._state.dialogType;
1589
+ const prevStepIndex = this._state.stepFormCurrentIndex;
794
1590
  if (s.open && !wasOpen) {
1591
+ this._isClosing = false;
1592
+ clearTimeout(this._closeTimer);
795
1593
  window.addEventListener("beforeunload", this._beforeUnloadHandler);
796
1594
  } else if (!s.open && wasOpen) {
1595
+ this._isClosing = true;
1596
+ clearTimeout(this._closeTimer);
1597
+ this._closeTimer = setTimeout(() => {
1598
+ this._isClosing = false;
1599
+ }, 320);
797
1600
  window.removeEventListener("beforeunload", this._beforeUnloadHandler);
798
1601
  }
1602
+ if (s.open && s.dialogType !== prevDialogType) {
1603
+ this._bodyKey++;
1604
+ } else if (s.open && s.dialogType === "step-form" && s.stepFormCurrentIndex !== prevStepIndex) {
1605
+ this._bodyKey++;
1606
+ }
1607
+ this._state = s;
1608
+ this._syncBodyScroll(s.open);
799
1609
  });
800
1610
  }
801
1611
  window.addEventListener("keydown", this._onKeyDown);
@@ -804,6 +1614,7 @@ var OverlayDialog = class extends LitElement {
804
1614
  super.disconnectedCallback();
805
1615
  this._unsubscribe?.();
806
1616
  this._syncBodyScroll(false);
1617
+ clearTimeout(this._closeTimer);
807
1618
  window.removeEventListener("beforeunload", this._beforeUnloadHandler);
808
1619
  window.removeEventListener("keydown", this._onKeyDown);
809
1620
  }
@@ -879,9 +1690,11 @@ var OverlayDialog = class extends LitElement {
879
1690
  }
880
1691
  _renderSpinner() {
881
1692
  return html`
882
- <div class="spinner">
883
- <div class="spinner-half">
884
- <div class="spinner-inner"></div>
1693
+ <div class="spinner-wrap">
1694
+ <div class="spinner">
1695
+ <div class="spinner-half">
1696
+ <div class="spinner-inner"></div>
1697
+ </div>
885
1698
  </div>
886
1699
  </div>
887
1700
  `;
@@ -1019,6 +1832,187 @@ var OverlayDialog = class extends LitElement {
1019
1832
  </ul>
1020
1833
  `;
1021
1834
  }
1835
+ // ─── Form Helpers ────────────────────────────────────────
1836
+ _createFormContext() {
1837
+ const s = this._state;
1838
+ return {
1839
+ getValue: (k) => s.formValues[k],
1840
+ getError: (k) => s.formErrors[k] ?? "",
1841
+ getTouched: (k) => !!s.formTouched[k],
1842
+ onUpdate: (k, v) => this.controller.updateFormField(k, v),
1843
+ onBlur: (k) => this.controller.touchFormField(k)
1844
+ };
1845
+ }
1846
+ _createStepFormContext() {
1847
+ const s = this._state;
1848
+ const step = s.stepFormSteps[s.stepFormCurrentIndex];
1849
+ return {
1850
+ getValue: (k) => step?.values[k],
1851
+ getError: (k) => step?.errors[k] ?? "",
1852
+ getTouched: (k) => !!step?.touched[k],
1853
+ onUpdate: (k, v) => this.controller.updateStepFormField(k, v),
1854
+ onBlur: (k) => this.controller.touchStepFormField(k)
1855
+ };
1856
+ }
1857
+ _getOrderedFields(fields, layout) {
1858
+ const order = layout.fieldOrder;
1859
+ if (!order?.length) return fields;
1860
+ const fieldMap = new Map(fields.map((f) => [f.key, f]));
1861
+ const ordered = [];
1862
+ for (const key of order) {
1863
+ const f = fieldMap.get(key);
1864
+ if (f) {
1865
+ ordered.push(f);
1866
+ fieldMap.delete(key);
1867
+ }
1868
+ }
1869
+ for (const f of fieldMap.values()) {
1870
+ ordered.push(f);
1871
+ }
1872
+ return ordered;
1873
+ }
1874
+ _renderFormGrid(fields, columns, gap, ctx) {
1875
+ return html`
1876
+ <div class="form-grid" style="--dialog-form-columns:${columns}; gap:${gap}">
1877
+ ${fields.map((f) => this._renderFormField(f, ctx))}
1878
+ </div>
1879
+ `;
1880
+ }
1881
+ _renderGroupedForm(allFields, groups, layout, ctx) {
1882
+ const fieldMap = new Map(allFields.map((f) => [f.key, f]));
1883
+ const usedKeys = /* @__PURE__ */ new Set();
1884
+ const gap = layout.gap ?? "16px";
1885
+ const groupFragments = groups.map((group) => {
1886
+ const groupFields = [];
1887
+ for (const key of group.fields) {
1888
+ const f = fieldMap.get(key);
1889
+ if (f) {
1890
+ groupFields.push(f);
1891
+ usedKeys.add(key);
1892
+ }
1893
+ }
1894
+ if (!groupFields.length) return nothing;
1895
+ const cols = group.columns ?? layout.columns ?? 1;
1896
+ return html`
1897
+ <fieldset class="form-group">
1898
+ ${group.label ? html`<legend class="form-group-label">${group.label}</legend>` : nothing}
1899
+ ${this._renderFormGrid(groupFields, cols, gap, ctx)}
1900
+ </fieldset>
1901
+ `;
1902
+ });
1903
+ const remaining = allFields.filter((f) => !usedKeys.has(f.key));
1904
+ return html`
1905
+ ${groupFragments}
1906
+ ${remaining.length ? this._renderFormGrid(remaining, layout.columns ?? 1, gap, ctx) : nothing}
1907
+ `;
1908
+ }
1909
+ _renderForm(fields, layout, ctx) {
1910
+ const ordered = this._getOrderedFields(fields, layout);
1911
+ const gap = layout.gap ?? "16px";
1912
+ if (layout.groups?.length) {
1913
+ return this._renderGroupedForm(ordered, layout.groups, layout, ctx);
1914
+ }
1915
+ return this._renderFormGrid(ordered, layout.columns ?? 1, gap, ctx);
1916
+ }
1917
+ _renderFormField(field, ctx) {
1918
+ const value = ctx.getValue(field.key);
1919
+ const error = ctx.getError(field.key);
1920
+ const touched = ctx.getTouched(field.key);
1921
+ const showError = touched && !!error;
1922
+ if (field.inputType === "checkbox") {
1923
+ return html`
1924
+ <div class="form-field" data-type="checkbox" ?data-error=${showError}>
1925
+ <label class="form-checkbox-label">
1926
+ <input
1927
+ type="checkbox"
1928
+ class="form-checkbox"
1929
+ .checked=${!!value}
1930
+ @change=${(e) => ctx.onUpdate(field.key, e.target.checked)}
1931
+ @blur=${() => ctx.onBlur(field.key)}
1932
+ />
1933
+ <span class="form-checkbox-text">
1934
+ ${field.label}
1935
+ ${field.required ? html`<span class="form-required">*</span>` : nothing}
1936
+ </span>
1937
+ </label>
1938
+ ${showError ? html`<span class="form-error">${error}</span>` : nothing}
1939
+ </div>
1940
+ `;
1941
+ }
1942
+ return html`
1943
+ <div class="form-field" data-type=${field.inputType} ?data-error=${showError}>
1944
+ <label class="form-label" for="form-${field.key}">
1945
+ ${field.label} ${field.required ? html`<span class="form-required">*</span>` : nothing}
1946
+ </label>
1947
+ ${field.description ? html`<span class="form-hint">${field.description}</span>` : nothing}
1948
+ ${this._renderFormInput(field, value, ctx)}
1949
+ ${showError ? html`<span class="form-error">${error}</span>` : nothing}
1950
+ </div>
1951
+ `;
1952
+ }
1953
+ _renderFormInput(field, value, ctx) {
1954
+ switch (field.inputType) {
1955
+ case "select":
1956
+ return html`
1957
+ <select
1958
+ class="form-select"
1959
+ id="form-${field.key}"
1960
+ @change=${(e) => ctx.onUpdate(field.key, e.target.value)}
1961
+ @blur=${() => ctx.onBlur(field.key)}
1962
+ >
1963
+ <option value="" ?selected=${!value}>選択してください</option>
1964
+ ${field.options.map(
1965
+ (opt) => html`<option value=${opt} ?selected=${value === opt}>${opt}</option>`
1966
+ )}
1967
+ </select>
1968
+ `;
1969
+ case "number":
1970
+ return html`
1971
+ <input
1972
+ type="number"
1973
+ class="form-input"
1974
+ id="form-${field.key}"
1975
+ .value=${value != null ? String(value) : ""}
1976
+ min=${field.min ?? nothing}
1977
+ max=${field.max ?? nothing}
1978
+ placeholder=${field.placeholder || nothing}
1979
+ @input=${(e) => {
1980
+ const v = e.target.valueAsNumber;
1981
+ ctx.onUpdate(field.key, Number.isNaN(v) ? void 0 : v);
1982
+ }}
1983
+ @blur=${() => ctx.onBlur(field.key)}
1984
+ />
1985
+ `;
1986
+ case "date":
1987
+ return html`
1988
+ <input
1989
+ type="date"
1990
+ class="form-input"
1991
+ id="form-${field.key}"
1992
+ .value=${value != null ? String(value) : ""}
1993
+ @input=${(e) => {
1994
+ const str = e.target.value;
1995
+ ctx.onUpdate(field.key, str || void 0);
1996
+ }}
1997
+ @blur=${() => ctx.onBlur(field.key)}
1998
+ />
1999
+ `;
2000
+ default:
2001
+ return html`
2002
+ <input
2003
+ type=${field.inputType}
2004
+ class="form-input"
2005
+ id="form-${field.key}"
2006
+ .value=${value ?? ""}
2007
+ minlength=${field.minLength ?? nothing}
2008
+ maxlength=${field.maxLength ?? nothing}
2009
+ placeholder=${field.placeholder || nothing}
2010
+ @input=${(e) => ctx.onUpdate(field.key, e.target.value)}
2011
+ @blur=${() => ctx.onBlur(field.key)}
2012
+ />
2013
+ `;
2014
+ }
2015
+ }
1022
2016
  _renderButtons() {
1023
2017
  const s = this._state;
1024
2018
  if (!s.showConfirmButton && !s.showCancelButton) return nothing;
@@ -1033,6 +2027,39 @@ var OverlayDialog = class extends LitElement {
1033
2027
  </div>
1034
2028
  `;
1035
2029
  }
2030
+ // ─── Step Form Helpers ───────────────────────────────────
2031
+ _renderStepFormButtons() {
2032
+ const s = this._state;
2033
+ const isFirst = s.stepFormCurrentIndex === 0;
2034
+ const isLast = s.stepFormCurrentIndex === s.stepFormSteps.length - 1;
2035
+ const submitText = isLast ? s.stepFormSubmitText : s.stepFormNextText;
2036
+ return html`
2037
+ <div class="actions actions-step-form">
2038
+ <button class="btn btn-cancel" @click=${() => this.controller.onCancel()}>
2039
+ ${s.cancelButtonText}
2040
+ </button>
2041
+ <div class="step-form-nav">
2042
+ ${isFirst ? nothing : html`
2043
+ <button class="btn btn-prev" @click=${() => this.controller.onStepPrev()}>
2044
+ ${s.stepFormPrevText}
2045
+ </button>
2046
+ `}
2047
+ <button class="btn btn-confirm" @click=${() => this.controller.onStepNext()}>
2048
+ ${submitText}
2049
+ </button>
2050
+ </div>
2051
+ </div>
2052
+ `;
2053
+ }
2054
+ _deriveStepItems() {
2055
+ const s = this._state;
2056
+ return s.stepFormSteps.map((st, i) => {
2057
+ let status = "pending";
2058
+ if (i < s.stepFormCurrentIndex) status = "done";
2059
+ else if (i === s.stepFormCurrentIndex) status = "active";
2060
+ return { key: st.key, label: st.label, status };
2061
+ });
2062
+ }
1036
2063
  _renderBody() {
1037
2064
  const s = this._state;
1038
2065
  switch (s.dialogType) {
@@ -1059,18 +2086,51 @@ var OverlayDialog = class extends LitElement {
1059
2086
  ${s.label ? html`<p class="label">${s.label}</p>` : nothing}
1060
2087
  ${this._renderStepsList(s.steps)}
1061
2088
  `;
2089
+ case "form": {
2090
+ const ctx = this._createFormContext();
2091
+ return html`
2092
+ ${s.title ? html`<p class="label">${s.title}</p>` : nothing}
2093
+ ${s.description ? html`<p class="description">${s.description}</p>` : nothing}
2094
+ <div class="form-scroll-container">
2095
+ ${this._renderForm(s.formFields, s.formLayout, ctx)}
2096
+ </div>
2097
+ ${this._renderButtons()}
2098
+ `;
2099
+ }
2100
+ case "step-form": {
2101
+ const stepItems = this._deriveStepItems();
2102
+ const currentStep = s.stepFormSteps[s.stepFormCurrentIndex];
2103
+ if (!currentStep) return html`${nothing}`;
2104
+ const ctx = this._createStepFormContext();
2105
+ const stepCount = s.stepFormSteps.length;
2106
+ const counterText = `${s.stepFormCurrentIndex + 1} / ${stepCount}`;
2107
+ return html`
2108
+ ${this._renderStepsHeader(stepItems)}
2109
+ <p class="step-form-counter">${counterText}</p>
2110
+ <p class="label">${currentStep.label}</p>
2111
+ ${currentStep.description ? html`<p class="description">${currentStep.description}</p>` : nothing}
2112
+ ${currentStep.fields.length ? html`
2113
+ <div class="form-scroll-container">
2114
+ ${this._renderForm(currentStep.fields, currentStep.layout, ctx)}
2115
+ </div>
2116
+ ` : nothing}
2117
+ ${this._renderStepFormButtons()}
2118
+ `;
2119
+ }
1062
2120
  default:
1063
2121
  return html`${nothing}`;
1064
2122
  }
1065
2123
  }
1066
2124
  render() {
1067
2125
  const s = this._state;
1068
- const showHeaderTitle = s.title && s.dialogType !== "alert" && s.dialogType !== "confirm";
2126
+ const showHeaderTitle = s.title && s.dialogType !== "alert" && s.dialogType !== "confirm" && s.dialogType !== "form" && s.dialogType !== "step-form";
1069
2127
  return html`
1070
2128
  <div class="backdrop" ?data-open=${s.open} @click=${this._onBackdropClick}>
1071
- <div class="card">
2129
+ <div class="card" data-type=${s.dialogType} ?data-closing=${this._isClosing}>
1072
2130
  ${showHeaderTitle ? html`<p class="dialog-title">${s.title}</p>` : nothing}
1073
- <div class="card-body">${this._renderBody()}</div>
2131
+ <div class="card-body">
2132
+ ${keyed(this._bodyKey, html`<div class="body-inner">${this._renderBody()}</div>`)}
2133
+ </div>
1074
2134
  <div
1075
2135
  class="progress-bar"
1076
2136
  style="width:${s.progress ?? 0}%;opacity:${s.progress !== null ? 1 : 0}"
@@ -1087,6 +2147,12 @@ __decorateClass([
1087
2147
  __decorateClass([
1088
2148
  state()
1089
2149
  ], OverlayDialog.prototype, "_state", 2);
2150
+ __decorateClass([
2151
+ state()
2152
+ ], OverlayDialog.prototype, "_bodyKey", 2);
2153
+ __decorateClass([
2154
+ state()
2155
+ ], OverlayDialog.prototype, "_isClosing", 2);
1090
2156
  OverlayDialog = __decorateClass([
1091
2157
  customElement("overlay-dialog")
1092
2158
  ], OverlayDialog);
@@ -1116,6 +2182,28 @@ var DialogSingleton = class {
1116
2182
  __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
1117
2183
  return __privateGet(this, _controller).confirm(optionsOrLabel);
1118
2184
  }
2185
+ // ─── Form ──────────────────────────────────────────────────
2186
+ form(schema, options) {
2187
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
2188
+ return __privateGet(this, _controller).form(schema, options);
2189
+ }
2190
+ // ─── Step Form ─────────────────────────────────────────────
2191
+ showStepForm(steps, options) {
2192
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
2193
+ return __privateGet(this, _controller).showStepForm(steps, options);
2194
+ }
2195
+ onStepNext() {
2196
+ __privateGet(this, _controller).onStepNext();
2197
+ }
2198
+ onStepPrev() {
2199
+ __privateGet(this, _controller).onStepPrev();
2200
+ }
2201
+ updateStepFormField(fieldKey, value) {
2202
+ __privateGet(this, _controller).updateStepFormField(fieldKey, value);
2203
+ }
2204
+ touchStepFormField(fieldKey) {
2205
+ __privateGet(this, _controller).touchStepFormField(fieldKey);
2206
+ }
1119
2207
  // ─── Loading helpers ─────────────────────────────────────
1120
2208
  showLoading(label) {
1121
2209
  __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);