@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.cjs CHANGED
@@ -60,14 +60,168 @@ var createInitialState = () => ({
60
60
  queues: [],
61
61
  steps: [],
62
62
  timer: null,
63
- title: ""
63
+ title: "",
64
+ formFields: [],
65
+ formValues: {},
66
+ formErrors: {},
67
+ formTouched: {},
68
+ formLayout: {},
69
+ formValidateOnChange: true,
70
+ formValidateOnBlur: true,
71
+ stepFormSteps: [],
72
+ stepFormCurrentIndex: 0,
73
+ stepFormNextText: "\u6B21\u3078",
74
+ stepFormPrevText: "\u623B\u308B",
75
+ stepFormSubmitText: "OK"
64
76
  });
65
77
 
78
+ // src/zod-utils.ts
79
+ function resolveTypeName(field) {
80
+ const raw = field._def.typeName ?? field._def.type;
81
+ if (!raw) return "";
82
+ const v4Map = {
83
+ string: "ZodString",
84
+ number: "ZodNumber",
85
+ boolean: "ZodBoolean",
86
+ enum: "ZodEnum",
87
+ date: "ZodDate",
88
+ optional: "ZodOptional",
89
+ nullable: "ZodNullable",
90
+ default: "ZodDefault",
91
+ object: "ZodObject",
92
+ pipe: "ZodPipe"
93
+ };
94
+ return v4Map[raw] ?? raw;
95
+ }
96
+ function unwrapType(zodType) {
97
+ let inner = zodType;
98
+ let required = true;
99
+ let defaultValue = void 0;
100
+ let description = zodType.description ?? "";
101
+ while (true) {
102
+ const typeName = resolveTypeName(inner);
103
+ if (typeName === "ZodOptional" || typeName === "ZodNullable") {
104
+ required = false;
105
+ inner = inner._def.innerType;
106
+ } else if (typeName === "ZodDefault") {
107
+ const raw = inner._def.defaultValue;
108
+ defaultValue = typeof raw === "function" ? raw() : raw;
109
+ inner = inner._def.innerType;
110
+ } else if (typeName === "ZodEffects") {
111
+ inner = inner._def.schema;
112
+ } else {
113
+ break;
114
+ }
115
+ if (!description && inner.description) {
116
+ description = inner.description;
117
+ }
118
+ }
119
+ return { inner, required, defaultValue, description };
120
+ }
121
+ function extractFieldMeta(key, zodType) {
122
+ const { inner, required, defaultValue, description } = unwrapType(zodType);
123
+ const typeName = resolveTypeName(inner);
124
+ let inputType;
125
+ let options = [];
126
+ let min;
127
+ let max;
128
+ let minLength;
129
+ let maxLength;
130
+ switch (typeName) {
131
+ case "ZodString": {
132
+ inputType = "text";
133
+ if (inner.format === "email") inputType = "email";
134
+ else if (inner.format === "url") inputType = "url";
135
+ if (inner.minLength != null) minLength = inner.minLength;
136
+ if (inner.maxLength != null) maxLength = inner.maxLength;
137
+ const checks = inner._def.checks ?? [];
138
+ for (const check of checks) {
139
+ const kind = check.kind ?? check.def?.check;
140
+ const fmt = check.format ?? check.def?.format;
141
+ if (!fmt && (kind === "email" || fmt === "email")) inputType = "email";
142
+ else if (!fmt && (kind === "url" || fmt === "url")) inputType = "url";
143
+ else if (fmt === "email" && inputType === "text") inputType = "email";
144
+ else if (fmt === "url" && inputType === "text") inputType = "url";
145
+ if (kind === "min" && check.value != null && minLength == null) minLength = check.value;
146
+ if (kind === "max" && check.value != null && maxLength == null) maxLength = check.value;
147
+ }
148
+ break;
149
+ }
150
+ case "ZodNumber": {
151
+ inputType = "number";
152
+ if (inner.minValue != null) min = inner.minValue;
153
+ if (inner.maxValue != null) max = inner.maxValue;
154
+ if (min == null || max == null) {
155
+ const checks = inner._def.checks ?? [];
156
+ for (const check of checks) {
157
+ if (check.kind === "min" && check.value != null && min == null) min = check.value;
158
+ if (check.kind === "max" && check.value != null && max == null) max = check.value;
159
+ }
160
+ }
161
+ break;
162
+ }
163
+ case "ZodBoolean":
164
+ inputType = "checkbox";
165
+ break;
166
+ case "ZodEnum": {
167
+ inputType = "select";
168
+ if (inner.options?.length) {
169
+ options = [...inner.options];
170
+ } else if (inner._def.entries) {
171
+ options = Object.values(inner._def.entries);
172
+ } else if (inner._def.values?.length) {
173
+ options = [...inner._def.values];
174
+ }
175
+ break;
176
+ }
177
+ case "ZodNativeEnum": {
178
+ inputType = "select";
179
+ const enumValues = inner._def.values;
180
+ if (enumValues) {
181
+ options = Object.values(enumValues).filter((v) => typeof v === "string");
182
+ }
183
+ break;
184
+ }
185
+ case "ZodDate":
186
+ inputType = "date";
187
+ break;
188
+ default:
189
+ return null;
190
+ }
191
+ const label = description || key;
192
+ return {
193
+ key,
194
+ inputType,
195
+ label,
196
+ description: description && description !== label ? description : "",
197
+ required,
198
+ options,
199
+ placeholder: "",
200
+ min,
201
+ max,
202
+ minLength,
203
+ maxLength,
204
+ defaultValue
205
+ };
206
+ }
207
+ function extractFormFields(schema) {
208
+ const shapeDef = schema._def.shape;
209
+ if (!shapeDef) return [];
210
+ const shape = typeof shapeDef === "function" ? shapeDef() : shapeDef;
211
+ if (!shape) return [];
212
+ const fields = [];
213
+ for (const [key, zodType] of Object.entries(shape)) {
214
+ const meta = extractFieldMeta(key, zodType);
215
+ if (meta) fields.push(meta);
216
+ }
217
+ return fields;
218
+ }
219
+
66
220
  // src/controller.ts
67
221
  function normalizeItemInput(input) {
68
222
  return typeof input === "string" ? { key: input, label: input } : input;
69
223
  }
70
- var _state, _listeners, _resolver, _timerId, _DialogController_instances, emit_fn, update_fn, createPromise_fn, resolve_fn, clearTimer_fn, updateItemStatus_fn;
224
+ 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;
71
225
  var DialogController = class {
72
226
  constructor() {
73
227
  __privateAdd(this, _DialogController_instances);
@@ -75,6 +229,10 @@ var DialogController = class {
75
229
  __privateAdd(this, _listeners, /* @__PURE__ */ new Set());
76
230
  __privateAdd(this, _resolver, null);
77
231
  __privateAdd(this, _timerId, null);
232
+ __privateAdd(this, _formSchema, null);
233
+ __privateAdd(this, _formResult, null);
234
+ __privateAdd(this, _stepFormSchemas, []);
235
+ __privateAdd(this, _stepFormResults, {});
78
236
  __privateSet(this, _state, createInitialState());
79
237
  }
80
238
  // ─── Observable ──────────────────────────────────────────
@@ -220,9 +378,202 @@ var DialogController = class {
220
378
  clearSteps() {
221
379
  __privateMethod(this, _DialogController_instances, update_fn).call(this, { steps: [] });
222
380
  }
381
+ // ─── Form ─────────────────────────────────────────────────
382
+ form(schema, options) {
383
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
384
+ __privateSet(this, _formSchema, schema);
385
+ __privateSet(this, _formResult, null);
386
+ const fields = extractFormFields(schema);
387
+ const defaultValues = {};
388
+ for (const field of fields) {
389
+ if (field.defaultValue !== void 0) {
390
+ defaultValues[field.key] = field.defaultValue;
391
+ }
392
+ }
393
+ if (options?.defaultValues) {
394
+ Object.assign(defaultValues, options.defaultValues);
395
+ }
396
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, {
397
+ open: true,
398
+ dialogType: "form",
399
+ title: options?.title ?? "",
400
+ label: "",
401
+ description: options?.description ?? "",
402
+ icon: null,
403
+ showConfirmButton: true,
404
+ showCancelButton: true,
405
+ confirmButtonText: options?.confirmButtonText ?? "OK",
406
+ cancelButtonText: options?.cancelButtonText ?? "\u30AD\u30E3\u30F3\u30BB\u30EB",
407
+ allowOutsideClick: options?.allowOutsideClick ?? false,
408
+ allowEscapeKey: options?.allowEscapeKey ?? true,
409
+ progress: null,
410
+ timer: null,
411
+ formFields: fields,
412
+ formValues: defaultValues,
413
+ formErrors: {},
414
+ formTouched: {},
415
+ formLayout: options?.layout ?? {},
416
+ formValidateOnChange: options?.validateOnChange ?? true,
417
+ formValidateOnBlur: options?.validateOnBlur ?? true
418
+ });
419
+ return __privateMethod(this, _DialogController_instances, createPromise_fn).call(this, null).then((r) => {
420
+ const data = __privateGet(this, _formResult);
421
+ __privateSet(this, _formSchema, null);
422
+ __privateSet(this, _formResult, null);
423
+ return r.isConfirmed ? data : null;
424
+ });
425
+ }
426
+ updateFormField(key, value) {
427
+ const formValues = { ...__privateGet(this, _state).formValues, [key]: value };
428
+ const formTouched = { ...__privateGet(this, _state).formTouched, [key]: true };
429
+ let formErrors = { ...__privateGet(this, _state).formErrors };
430
+ if (__privateGet(this, _formSchema) && __privateGet(this, _state).formValidateOnChange) {
431
+ formErrors = __privateMethod(this, _DialogController_instances, validateFormField_fn).call(this, key, formValues, formErrors);
432
+ }
433
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { formValues, formTouched, formErrors });
434
+ }
435
+ touchFormField(key) {
436
+ const formTouched = { ...__privateGet(this, _state).formTouched, [key]: true };
437
+ let formErrors = { ...__privateGet(this, _state).formErrors };
438
+ if (__privateGet(this, _formSchema) && __privateGet(this, _state).formValidateOnBlur) {
439
+ formErrors = __privateMethod(this, _DialogController_instances, validateFormField_fn).call(this, key, __privateGet(this, _state).formValues, formErrors);
440
+ }
441
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { formTouched, formErrors });
442
+ }
443
+ // ─── Step Form ───────────────────────────────────────────
444
+ showStepForm(steps, options) {
445
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
446
+ __privateSet(this, _stepFormSchemas, steps.map((s) => s.schema ?? null));
447
+ __privateSet(this, _stepFormResults, {});
448
+ const stepFormSteps = steps.map((s) => {
449
+ const fields = s.schema ? extractFormFields(s.schema) : [];
450
+ const values = {};
451
+ for (const f of fields) {
452
+ if (f.defaultValue !== void 0) values[f.key] = f.defaultValue;
453
+ }
454
+ if (s.defaultValues) Object.assign(values, s.defaultValues);
455
+ return {
456
+ key: s.key,
457
+ label: s.label,
458
+ description: s.description ?? "",
459
+ fields,
460
+ values,
461
+ errors: {},
462
+ touched: {},
463
+ layout: s.layout ?? {}
464
+ };
465
+ });
466
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, {
467
+ open: true,
468
+ dialogType: "step-form",
469
+ title: options?.title ?? "",
470
+ label: "",
471
+ description: "",
472
+ icon: null,
473
+ showConfirmButton: false,
474
+ showCancelButton: false,
475
+ allowOutsideClick: options?.allowOutsideClick ?? false,
476
+ allowEscapeKey: options?.allowEscapeKey ?? true,
477
+ progress: null,
478
+ timer: null,
479
+ stepFormSteps,
480
+ stepFormCurrentIndex: 0,
481
+ stepFormNextText: options?.nextButtonText ?? "\u6B21\u3078",
482
+ stepFormPrevText: options?.prevButtonText ?? "\u623B\u308B",
483
+ stepFormSubmitText: options?.submitButtonText ?? "OK",
484
+ cancelButtonText: options?.cancelButtonText ?? "\u30AD\u30E3\u30F3\u30BB\u30EB"
485
+ });
486
+ return __privateMethod(this, _DialogController_instances, createPromise_fn).call(this, null).then((r) => {
487
+ const data = __privateGet(this, _stepFormResults);
488
+ __privateSet(this, _stepFormSchemas, []);
489
+ __privateSet(this, _stepFormResults, {});
490
+ return r.isConfirmed ? data : null;
491
+ });
492
+ }
493
+ onStepNext() {
494
+ const s = __privateGet(this, _state);
495
+ const idx = s.stepFormCurrentIndex;
496
+ const step = s.stepFormSteps[idx];
497
+ if (!step) return;
498
+ const schema = __privateGet(this, _stepFormSchemas)[idx];
499
+ if (schema) {
500
+ const result = schema.safeParse(step.values);
501
+ if (!result.success) {
502
+ const errors = {};
503
+ for (const issue of result.error.issues) {
504
+ const key = issue.path[0]?.toString();
505
+ if (key && !errors[key]) errors[key] = issue.message;
506
+ }
507
+ const touched = {};
508
+ for (const f of step.fields) touched[f.key] = true;
509
+ __privateMethod(this, _DialogController_instances, updateCurrentStep_fn).call(this, { errors, touched });
510
+ return;
511
+ }
512
+ __privateGet(this, _stepFormResults)[step.key] = result.data;
513
+ }
514
+ const isLast = idx === s.stepFormSteps.length - 1;
515
+ if (isLast) {
516
+ const r = { isConfirmed: true, isCanceled: false, isDismissed: false };
517
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { ...createInitialState(), open: false });
518
+ __privateMethod(this, _DialogController_instances, resolve_fn).call(this, r);
519
+ return;
520
+ }
521
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { stepFormCurrentIndex: idx + 1 });
522
+ }
523
+ onStepPrev() {
524
+ const idx = __privateGet(this, _state).stepFormCurrentIndex;
525
+ if (idx <= 0) return;
526
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { stepFormCurrentIndex: idx - 1 });
527
+ }
528
+ updateStepFormField(fieldKey, value) {
529
+ const s = __privateGet(this, _state);
530
+ const idx = s.stepFormCurrentIndex;
531
+ const step = s.stepFormSteps[idx];
532
+ if (!step) return;
533
+ const values = { ...step.values, [fieldKey]: value };
534
+ const touched = { ...step.touched, [fieldKey]: true };
535
+ let errors = { ...step.errors };
536
+ const schema = __privateGet(this, _stepFormSchemas)[idx];
537
+ if (schema && s.formValidateOnChange) {
538
+ errors = __privateMethod(this, _DialogController_instances, validateStepField_fn).call(this, schema, fieldKey, values, errors);
539
+ }
540
+ __privateMethod(this, _DialogController_instances, updateCurrentStep_fn).call(this, { values, touched, errors });
541
+ }
542
+ touchStepFormField(fieldKey) {
543
+ const s = __privateGet(this, _state);
544
+ const idx = s.stepFormCurrentIndex;
545
+ const step = s.stepFormSteps[idx];
546
+ if (!step) return;
547
+ const touched = { ...step.touched, [fieldKey]: true };
548
+ let errors = { ...step.errors };
549
+ const schema = __privateGet(this, _stepFormSchemas)[idx];
550
+ if (schema && s.formValidateOnBlur) {
551
+ errors = __privateMethod(this, _DialogController_instances, validateStepField_fn).call(this, schema, fieldKey, step.values, errors);
552
+ }
553
+ __privateMethod(this, _DialogController_instances, updateCurrentStep_fn).call(this, { touched, errors });
554
+ }
223
555
  // ─── Button actions (called from the component) ──────────
224
556
  onConfirm() {
225
557
  __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
558
+ if (__privateGet(this, _state).dialogType === "form" && __privateGet(this, _formSchema)) {
559
+ const result = __privateGet(this, _formSchema).safeParse(__privateGet(this, _state).formValues);
560
+ if (!result.success) {
561
+ const formErrors = {};
562
+ for (const issue of result.error.issues) {
563
+ const key = issue.path[0]?.toString();
564
+ if (key && !formErrors[key]) {
565
+ formErrors[key] = issue.message;
566
+ }
567
+ }
568
+ const formTouched = {};
569
+ for (const field of __privateGet(this, _state).formFields) {
570
+ formTouched[field.key] = true;
571
+ }
572
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { formErrors, formTouched });
573
+ return;
574
+ }
575
+ __privateSet(this, _formResult, result.data);
576
+ }
226
577
  const r = { isConfirmed: true, isCanceled: false, isDismissed: false };
227
578
  __privateMethod(this, _DialogController_instances, update_fn).call(this, { ...createInitialState(), open: false });
228
579
  __privateMethod(this, _DialogController_instances, resolve_fn).call(this, r);
@@ -246,6 +597,10 @@ _state = new WeakMap();
246
597
  _listeners = new WeakMap();
247
598
  _resolver = new WeakMap();
248
599
  _timerId = new WeakMap();
600
+ _formSchema = new WeakMap();
601
+ _formResult = new WeakMap();
602
+ _stepFormSchemas = new WeakMap();
603
+ _stepFormResults = new WeakMap();
249
604
  _DialogController_instances = new WeakSet();
250
605
  emit_fn = function() {
251
606
  const snapshot = { ...__privateGet(this, _state) };
@@ -255,6 +610,47 @@ update_fn = function(patch) {
255
610
  Object.assign(__privateGet(this, _state), patch);
256
611
  __privateMethod(this, _DialogController_instances, emit_fn).call(this);
257
612
  };
613
+ validateFormField_fn = function(key, values, errors) {
614
+ const result = __privateGet(this, _formSchema).safeParse(values);
615
+ const updated = { ...errors };
616
+ if (result.success) {
617
+ delete updated[key];
618
+ } else {
619
+ const fieldIssue = result.error.issues.find(
620
+ (issue) => issue.path[0]?.toString() === key
621
+ );
622
+ if (fieldIssue) {
623
+ updated[key] = fieldIssue.message;
624
+ } else {
625
+ delete updated[key];
626
+ }
627
+ }
628
+ return updated;
629
+ };
630
+ updateCurrentStep_fn = function(patch) {
631
+ const idx = __privateGet(this, _state).stepFormCurrentIndex;
632
+ const stepFormSteps = __privateGet(this, _state).stepFormSteps.map(
633
+ (st, i) => i === idx ? { ...st, ...patch } : st
634
+ );
635
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { stepFormSteps });
636
+ };
637
+ validateStepField_fn = function(schema, fieldKey, values, errors) {
638
+ const result = schema.safeParse(values);
639
+ const updated = { ...errors };
640
+ if (result.success) {
641
+ delete updated[fieldKey];
642
+ } else {
643
+ const issue = result.error.issues.find(
644
+ (iss) => iss.path[0]?.toString() === fieldKey
645
+ );
646
+ if (issue) {
647
+ updated[fieldKey] = issue.message;
648
+ } else {
649
+ delete updated[fieldKey];
650
+ }
651
+ }
652
+ return updated;
653
+ };
258
654
  // ─── Internal ────────────────────────────────────────────
259
655
  createPromise_fn = function(timer) {
260
656
  return new Promise((resolve) => {
@@ -291,6 +687,7 @@ updateItemStatus_fn = function(key, itemKey, status) {
291
687
  // src/overlay-dialog.ts
292
688
  var import_lit2 = require("lit");
293
689
  var import_decorators = require("lit/decorators.js");
690
+ var import_keyed = require("lit/directives/keyed.js");
294
691
  var import_unsafe_html = require("lit/directives/unsafe-html.js");
295
692
 
296
693
  // src/styles.ts
@@ -307,7 +704,7 @@ var overlayStyles = import_lit.css`
307
704
  --dialog-z-index: 1000;
308
705
  --dialog-backdrop-color: rgb(255 255 255 / 0.73);
309
706
  --dialog-backdrop-blur: 4px;
310
- --dialog-transition-duration: 250ms;
707
+ --dialog-transition-duration: 280ms;
311
708
 
312
709
  /* Card */
313
710
  --dialog-card-bg: #fff;
@@ -342,6 +739,24 @@ var overlayStyles = import_lit.css`
342
739
  --dialog-spinner-track: rgb(59 130 246 / 0.2);
343
740
  --dialog-spinner-arc: var(--dialog-primary);
344
741
 
742
+ /* Form */
743
+ --dialog-form-width: 500px;
744
+ --dialog-form-max-height: 60vh;
745
+ --dialog-form-gap: 16px;
746
+ --dialog-form-columns: 1;
747
+ --dialog-form-label-color: #374151;
748
+ --dialog-form-label-size: 13px;
749
+ --dialog-form-label-weight: 500;
750
+ --dialog-form-input-bg: #fff;
751
+ --dialog-form-input-border: #d1d5db;
752
+ --dialog-form-input-border-focus: var(--dialog-primary);
753
+ --dialog-form-input-radius: 6px;
754
+ --dialog-form-input-padding: 8px 12px;
755
+ --dialog-form-input-font-size: 14px;
756
+ --dialog-form-error-color: var(--dialog-error);
757
+ --dialog-form-hint-color: #9ca3af;
758
+ --dialog-form-required-color: var(--dialog-error);
759
+
345
760
  display: contents;
346
761
  font-family: var(--dialog-font-family);
347
762
  color: var(--dialog-text-color);
@@ -397,7 +812,6 @@ var overlayStyles = import_lit.css`
397
812
  min-height: var(--dialog-card-min-height);
398
813
  position: relative;
399
814
  overflow: hidden;
400
- transition: all var(--dialog-transition-duration) ease;
401
815
  }
402
816
 
403
817
  @media (min-width: 640px) {
@@ -409,22 +823,99 @@ var overlayStyles = import_lit.css`
409
823
  }
410
824
  }
411
825
 
826
+ /* ─── Card Open / Close Animations ─── */
827
+
828
+ @keyframes card-enter {
829
+ from {
830
+ opacity: 0;
831
+ transform: translateY(28px) scale(0.96);
832
+ filter: blur(6px);
833
+ }
834
+ to {
835
+ opacity: 1;
836
+ transform: translateY(0) scale(1);
837
+ filter: blur(0);
838
+ }
839
+ }
840
+
841
+ @keyframes card-exit {
842
+ from {
843
+ opacity: 1;
844
+ transform: translateY(0) scale(1);
845
+ filter: blur(0);
846
+ }
847
+ to {
848
+ opacity: 0;
849
+ transform: translateY(14px) scale(0.97);
850
+ filter: blur(4px);
851
+ }
852
+ }
853
+
854
+ .backdrop[data-open] .card {
855
+ animation: card-enter 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
856
+ }
857
+
858
+ .card[data-closing] {
859
+ animation: card-exit 300ms cubic-bezier(0.4, 0, 1, 1) both;
860
+ pointer-events: none;
861
+ }
862
+
412
863
  /* ─── Card Body ─── */
413
864
 
414
865
  .card-body {
415
866
  flex: 1;
416
867
  display: flex;
417
868
  flex-direction: column;
418
- align-items: center;
869
+ align-items: stretch;
419
870
  justify-content: center;
871
+ width: 100%;
872
+ }
873
+
874
+ /* ─── Body Inner (animated content wrapper) ─── */
875
+
876
+ @keyframes body-enter {
877
+ from {
878
+ opacity: 0;
879
+ transform: translateY(10px);
880
+ filter: blur(3px);
881
+ }
882
+ to {
883
+ opacity: 1;
884
+ transform: translateY(0);
885
+ filter: blur(0);
886
+ }
887
+ }
888
+
889
+ .body-inner {
890
+ display: flex;
891
+ flex-direction: column;
892
+ align-items: center;
420
893
  gap: 16px;
421
894
  width: 100%;
895
+ animation: body-enter 380ms 60ms cubic-bezier(0.16, 1, 0.3, 1) both;
422
896
  }
423
897
 
424
898
  /* ─── Spinner ─── */
425
899
 
426
- .spinner {
900
+ @keyframes spinner-enter {
901
+ from {
902
+ opacity: 0;
903
+ transform: scale(0.5);
904
+ }
905
+ to {
906
+ opacity: 1;
907
+ transform: scale(1);
908
+ }
909
+ }
910
+
911
+ .spinner-wrap {
427
912
  font-size: var(--dialog-spinner-size);
913
+ width: 1em;
914
+ height: 1em;
915
+ animation: spinner-enter 450ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
916
+ }
917
+
918
+ .spinner {
428
919
  width: 1em;
429
920
  height: 1em;
430
921
  border-radius: 50%;
@@ -465,6 +956,19 @@ var overlayStyles = import_lit.css`
465
956
 
466
957
  /* ─── Icon ─── */
467
958
 
959
+ @keyframes icon-enter {
960
+ from {
961
+ opacity: 0;
962
+ transform: scale(0.4) rotate(-12deg);
963
+ filter: blur(4px);
964
+ }
965
+ to {
966
+ opacity: 1;
967
+ transform: scale(1) rotate(0deg);
968
+ filter: blur(0);
969
+ }
970
+ }
971
+
468
972
  .icon-container {
469
973
  width: 64px;
470
974
  height: 64px;
@@ -473,6 +977,7 @@ var overlayStyles = import_lit.css`
473
977
  align-items: center;
474
978
  justify-content: center;
475
979
  flex-shrink: 0;
980
+ animation: icon-enter 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
476
981
  }
477
982
 
478
983
  .icon-container svg {
@@ -598,6 +1103,7 @@ var overlayStyles = import_lit.css`
598
1103
  gap: 8px;
599
1104
  width: 100%;
600
1105
  margin-top: 8px;
1106
+ animation: body-enter 320ms 160ms cubic-bezier(0.16, 1, 0.3, 1) both;
601
1107
  }
602
1108
 
603
1109
  @media (min-width: 640px) {
@@ -617,7 +1123,8 @@ var overlayStyles = import_lit.css`
617
1123
  font-weight: 500;
618
1124
  transition:
619
1125
  background-color 150ms ease,
620
- transform 80ms ease;
1126
+ transform 80ms ease,
1127
+ box-shadow 150ms ease;
621
1128
  min-width: 100px;
622
1129
  text-align: center;
623
1130
  }
@@ -633,6 +1140,7 @@ var overlayStyles = import_lit.css`
633
1140
 
634
1141
  .btn-confirm:hover {
635
1142
  background-color: var(--dialog-primary-hover);
1143
+ box-shadow: 0 4px 12px rgb(59 130 246 / 0.35);
636
1144
  }
637
1145
 
638
1146
  .btn-cancel {
@@ -656,11 +1164,48 @@ var overlayStyles = import_lit.css`
656
1164
  list-style: none;
657
1165
  }
658
1166
 
1167
+ @keyframes task-item-enter {
1168
+ from {
1169
+ opacity: 0;
1170
+ transform: translateX(-8px);
1171
+ }
1172
+ to {
1173
+ opacity: 1;
1174
+ transform: translateX(0);
1175
+ }
1176
+ }
1177
+
659
1178
  .task-item {
660
1179
  display: flex;
661
1180
  align-items: center;
662
1181
  gap: 12px;
663
1182
  font-size: 14px;
1183
+ animation: task-item-enter 300ms cubic-bezier(0.16, 1, 0.3, 1) both;
1184
+ }
1185
+
1186
+ .task-item:nth-child(1) {
1187
+ animation-delay: 40ms;
1188
+ }
1189
+ .task-item:nth-child(2) {
1190
+ animation-delay: 80ms;
1191
+ }
1192
+ .task-item:nth-child(3) {
1193
+ animation-delay: 120ms;
1194
+ }
1195
+ .task-item:nth-child(4) {
1196
+ animation-delay: 160ms;
1197
+ }
1198
+ .task-item:nth-child(5) {
1199
+ animation-delay: 200ms;
1200
+ }
1201
+ .task-item:nth-child(6) {
1202
+ animation-delay: 240ms;
1203
+ }
1204
+ .task-item:nth-child(7) {
1205
+ animation-delay: 280ms;
1206
+ }
1207
+ .task-item:nth-child(8) {
1208
+ animation-delay: 320ms;
664
1209
  }
665
1210
 
666
1211
  .task-icon {
@@ -710,6 +1255,7 @@ var overlayStyles = import_lit.css`
710
1255
 
711
1256
  .task-label {
712
1257
  color: #6b7280;
1258
+ transition: color 250ms ease;
713
1259
  }
714
1260
 
715
1261
  .task-label[data-status='active'] {
@@ -717,8 +1263,21 @@ var overlayStyles = import_lit.css`
717
1263
  font-weight: 500;
718
1264
  }
719
1265
 
1266
+ @keyframes task-done-flash {
1267
+ 0% {
1268
+ transform: translateX(0);
1269
+ }
1270
+ 30% {
1271
+ transform: translateX(4px);
1272
+ }
1273
+ 100% {
1274
+ transform: translateX(0);
1275
+ }
1276
+ }
1277
+
720
1278
  .task-label[data-status='done'] {
721
1279
  color: var(--dialog-success);
1280
+ animation: task-done-flash 350ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
722
1281
  }
723
1282
 
724
1283
  .task-label[data-status='error'] {
@@ -727,7 +1286,6 @@ var overlayStyles = import_lit.css`
727
1286
 
728
1287
  .task-label[data-status='skipped'] {
729
1288
  color: #9ca3af;
730
- text-decoration: line-through;
731
1289
  }
732
1290
 
733
1291
  /* ─── Queue ellipsis ─── */
@@ -763,17 +1321,54 @@ var overlayStyles = import_lit.css`
763
1321
  margin-bottom: 8px;
764
1322
  }
765
1323
 
1324
+ @keyframes step-dot-enter {
1325
+ from {
1326
+ opacity: 0;
1327
+ transform: scale(0.4);
1328
+ }
1329
+ to {
1330
+ opacity: 1;
1331
+ transform: scale(1);
1332
+ }
1333
+ }
1334
+
1335
+ @keyframes step-pulse {
1336
+ 0% {
1337
+ box-shadow:
1338
+ 0 0 0 0 rgb(59 130 246 / 0.5),
1339
+ 0 0 0 3px rgb(59 130 246 / 0.2);
1340
+ }
1341
+ 70% {
1342
+ box-shadow:
1343
+ 0 0 0 7px rgb(59 130 246 / 0),
1344
+ 0 0 0 3px rgb(59 130 246 / 0.2);
1345
+ }
1346
+ 100% {
1347
+ box-shadow:
1348
+ 0 0 0 0 rgb(59 130 246 / 0),
1349
+ 0 0 0 3px rgb(59 130 246 / 0.2);
1350
+ }
1351
+ }
1352
+
766
1353
  .step-dot {
767
1354
  width: 8px;
768
1355
  height: 8px;
769
1356
  border-radius: 50%;
770
1357
  background-color: #d1d5db;
771
- transition: background-color 200ms ease;
1358
+ transition:
1359
+ background-color 250ms ease,
1360
+ transform 250ms cubic-bezier(0.34, 1.56, 0.64, 1),
1361
+ width 250ms cubic-bezier(0.34, 1.56, 0.64, 1);
1362
+ animation: step-dot-enter 400ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
772
1363
  }
773
1364
 
774
1365
  .step-dot[data-status='active'] {
775
1366
  background-color: var(--dialog-primary);
776
- box-shadow: 0 0 0 3px rgb(59 130 246 / 0.2);
1367
+ width: 20px;
1368
+ border-radius: 4px;
1369
+ animation:
1370
+ step-dot-enter 400ms cubic-bezier(0.34, 1.56, 0.64, 1) both,
1371
+ step-pulse 2s 400ms infinite;
777
1372
  }
778
1373
 
779
1374
  .step-dot[data-status='done'] {
@@ -793,6 +1388,205 @@ var overlayStyles = import_lit.css`
793
1388
  max-width: 24px;
794
1389
  height: 2px;
795
1390
  background-color: #e5e7eb;
1391
+ transition: background-color 400ms ease;
1392
+ }
1393
+
1394
+ /* ─── Form ─── */
1395
+
1396
+ @media (min-width: 640px) {
1397
+ .card[data-type='form'] {
1398
+ width: var(--dialog-form-width);
1399
+ }
1400
+ }
1401
+
1402
+ .form-scroll-container {
1403
+ max-height: var(--dialog-form-max-height);
1404
+ overflow-y: auto;
1405
+ width: 100%;
1406
+ padding: 4px 0;
1407
+ }
1408
+
1409
+ .form-grid {
1410
+ display: grid;
1411
+ grid-template-columns: repeat(var(--dialog-form-columns, 1), 1fr);
1412
+ gap: var(--dialog-form-gap);
1413
+ width: 100%;
1414
+ }
1415
+
1416
+ @media (max-width: 639px) {
1417
+ .form-grid {
1418
+ grid-template-columns: 1fr !important;
1419
+ }
1420
+ }
1421
+
1422
+ .form-field {
1423
+ display: flex;
1424
+ flex-direction: column;
1425
+ gap: 4px;
1426
+ text-align: left;
1427
+ }
1428
+
1429
+ .form-field[data-type='checkbox'] {
1430
+ grid-column: 1 / -1;
1431
+ }
1432
+
1433
+ .form-label {
1434
+ font-size: var(--dialog-form-label-size);
1435
+ font-weight: var(--dialog-form-label-weight);
1436
+ color: var(--dialog-form-label-color);
1437
+ }
1438
+
1439
+ .form-required {
1440
+ color: var(--dialog-form-required-color);
1441
+ margin-left: 2px;
1442
+ }
1443
+
1444
+ .form-input,
1445
+ .form-select {
1446
+ padding: var(--dialog-form-input-padding);
1447
+ font-size: var(--dialog-form-input-font-size);
1448
+ font-family: inherit;
1449
+ background: var(--dialog-form-input-bg);
1450
+ border: 1px solid var(--dialog-form-input-border);
1451
+ border-radius: var(--dialog-form-input-radius);
1452
+ color: var(--dialog-text-color);
1453
+ outline: none;
1454
+ transition:
1455
+ border-color 150ms ease,
1456
+ box-shadow 150ms ease;
1457
+ width: 100%;
1458
+ box-sizing: border-box;
1459
+ }
1460
+
1461
+ .form-input:focus,
1462
+ .form-select:focus {
1463
+ border-color: var(--dialog-form-input-border-focus);
1464
+ box-shadow: 0 0 0 3px rgb(59 130 246 / 0.1);
1465
+ }
1466
+
1467
+ .form-field[data-error] .form-input,
1468
+ .form-field[data-error] .form-select {
1469
+ border-color: var(--dialog-form-error-color);
1470
+ }
1471
+
1472
+ .form-field[data-error] .form-input:focus,
1473
+ .form-field[data-error] .form-select:focus {
1474
+ box-shadow: 0 0 0 3px rgb(239 68 68 / 0.1);
1475
+ }
1476
+
1477
+ .form-error {
1478
+ font-size: 12px;
1479
+ color: var(--dialog-form-error-color);
1480
+ min-height: 1em;
1481
+ }
1482
+
1483
+ .form-hint {
1484
+ font-size: 12px;
1485
+ color: var(--dialog-form-hint-color);
1486
+ }
1487
+
1488
+ .form-checkbox {
1489
+ width: 18px;
1490
+ height: 18px;
1491
+ accent-color: var(--dialog-primary);
1492
+ cursor: pointer;
1493
+ flex-shrink: 0;
1494
+ }
1495
+
1496
+ .form-checkbox-label {
1497
+ display: flex;
1498
+ align-items: center;
1499
+ gap: 8px;
1500
+ cursor: pointer;
1501
+ font-size: var(--dialog-form-input-font-size);
1502
+ color: var(--dialog-form-label-color);
1503
+ }
1504
+
1505
+ .form-checkbox-text {
1506
+ font-size: var(--dialog-form-label-size);
1507
+ font-weight: var(--dialog-form-label-weight);
1508
+ color: var(--dialog-form-label-color);
1509
+ }
1510
+
1511
+ .form-group {
1512
+ border: 1px solid var(--dialog-card-border);
1513
+ border-radius: var(--dialog-form-input-radius);
1514
+ padding: 16px;
1515
+ margin: 0 0 8px;
1516
+ width: 100%;
1517
+ box-sizing: border-box;
1518
+ }
1519
+
1520
+ .form-group-label {
1521
+ font-size: var(--dialog-form-label-size);
1522
+ font-weight: 600;
1523
+ color: var(--dialog-form-label-color);
1524
+ padding: 0 4px;
1525
+ }
1526
+
1527
+ /* ─── Step Form ─── */
1528
+
1529
+ @media (min-width: 640px) {
1530
+ .card[data-type='step-form'] {
1531
+ width: var(--dialog-form-width);
1532
+ }
1533
+ }
1534
+
1535
+ .step-form-counter {
1536
+ font-size: 12px;
1537
+ color: var(--dialog-form-hint-color);
1538
+ text-align: center;
1539
+ margin: 0 0 4px;
1540
+ }
1541
+
1542
+ .actions-step-form {
1543
+ display: flex;
1544
+ flex-direction: row;
1545
+ justify-content: space-between;
1546
+ align-items: center;
1547
+ gap: 8px;
1548
+ margin-top: 20px;
1549
+ width: 100%;
1550
+ }
1551
+
1552
+ .step-form-nav {
1553
+ display: flex;
1554
+ flex-direction: row;
1555
+ gap: 8px;
1556
+ align-items: center;
1557
+ }
1558
+
1559
+ .btn-prev {
1560
+ padding: var(--dialog-btn-padding);
1561
+ font-size: var(--dialog-btn-font-size);
1562
+ font-family: inherit;
1563
+ font-weight: 500;
1564
+ border-radius: var(--dialog-btn-radius);
1565
+ cursor: pointer;
1566
+ border: 1px solid var(--dialog-form-input-border);
1567
+ background: transparent;
1568
+ color: var(--dialog-text-color);
1569
+ transition: background-color 120ms ease, border-color 120ms ease;
1570
+ }
1571
+
1572
+ .btn-prev:hover {
1573
+ background: #f9fafb;
1574
+ border-color: #9ca3af;
1575
+ }
1576
+
1577
+ .btn-prev:active {
1578
+ transform: scale(0.98);
1579
+ }
1580
+
1581
+ @media (max-width: 639px) {
1582
+ .actions-step-form {
1583
+ flex-direction: column-reverse;
1584
+ }
1585
+
1586
+ .step-form-nav {
1587
+ width: 100%;
1588
+ justify-content: flex-end;
1589
+ }
796
1590
  }
797
1591
  `;
798
1592
 
@@ -801,6 +1595,8 @@ var OverlayDialog = class extends import_lit2.LitElement {
801
1595
  constructor() {
802
1596
  super(...arguments);
803
1597
  this._state = createInitialState();
1598
+ this._bodyKey = 0;
1599
+ this._isClosing = false;
804
1600
  this._beforeUnloadHandler = (e) => e.preventDefault();
805
1601
  this._onKeyDown = (e) => {
806
1602
  if (e.key === "Escape" && this._state.open) {
@@ -814,13 +1610,27 @@ var OverlayDialog = class extends import_lit2.LitElement {
814
1610
  this._state = { ...this.controller.state };
815
1611
  this._unsubscribe = this.controller.subscribe((s) => {
816
1612
  const wasOpen = this._state.open;
817
- this._state = s;
818
- this._syncBodyScroll(s.open);
1613
+ const prevDialogType = this._state.dialogType;
1614
+ const prevStepIndex = this._state.stepFormCurrentIndex;
819
1615
  if (s.open && !wasOpen) {
1616
+ this._isClosing = false;
1617
+ clearTimeout(this._closeTimer);
820
1618
  window.addEventListener("beforeunload", this._beforeUnloadHandler);
821
1619
  } else if (!s.open && wasOpen) {
1620
+ this._isClosing = true;
1621
+ clearTimeout(this._closeTimer);
1622
+ this._closeTimer = setTimeout(() => {
1623
+ this._isClosing = false;
1624
+ }, 320);
822
1625
  window.removeEventListener("beforeunload", this._beforeUnloadHandler);
823
1626
  }
1627
+ if (s.open && s.dialogType !== prevDialogType) {
1628
+ this._bodyKey++;
1629
+ } else if (s.open && s.dialogType === "step-form" && s.stepFormCurrentIndex !== prevStepIndex) {
1630
+ this._bodyKey++;
1631
+ }
1632
+ this._state = s;
1633
+ this._syncBodyScroll(s.open);
824
1634
  });
825
1635
  }
826
1636
  window.addEventListener("keydown", this._onKeyDown);
@@ -829,6 +1639,7 @@ var OverlayDialog = class extends import_lit2.LitElement {
829
1639
  super.disconnectedCallback();
830
1640
  this._unsubscribe?.();
831
1641
  this._syncBodyScroll(false);
1642
+ clearTimeout(this._closeTimer);
832
1643
  window.removeEventListener("beforeunload", this._beforeUnloadHandler);
833
1644
  window.removeEventListener("keydown", this._onKeyDown);
834
1645
  }
@@ -904,9 +1715,11 @@ var OverlayDialog = class extends import_lit2.LitElement {
904
1715
  }
905
1716
  _renderSpinner() {
906
1717
  return import_lit2.html`
907
- <div class="spinner">
908
- <div class="spinner-half">
909
- <div class="spinner-inner"></div>
1718
+ <div class="spinner-wrap">
1719
+ <div class="spinner">
1720
+ <div class="spinner-half">
1721
+ <div class="spinner-inner"></div>
1722
+ </div>
910
1723
  </div>
911
1724
  </div>
912
1725
  `;
@@ -1044,6 +1857,187 @@ var OverlayDialog = class extends import_lit2.LitElement {
1044
1857
  </ul>
1045
1858
  `;
1046
1859
  }
1860
+ // ─── Form Helpers ────────────────────────────────────────
1861
+ _createFormContext() {
1862
+ const s = this._state;
1863
+ return {
1864
+ getValue: (k) => s.formValues[k],
1865
+ getError: (k) => s.formErrors[k] ?? "",
1866
+ getTouched: (k) => !!s.formTouched[k],
1867
+ onUpdate: (k, v) => this.controller.updateFormField(k, v),
1868
+ onBlur: (k) => this.controller.touchFormField(k)
1869
+ };
1870
+ }
1871
+ _createStepFormContext() {
1872
+ const s = this._state;
1873
+ const step = s.stepFormSteps[s.stepFormCurrentIndex];
1874
+ return {
1875
+ getValue: (k) => step?.values[k],
1876
+ getError: (k) => step?.errors[k] ?? "",
1877
+ getTouched: (k) => !!step?.touched[k],
1878
+ onUpdate: (k, v) => this.controller.updateStepFormField(k, v),
1879
+ onBlur: (k) => this.controller.touchStepFormField(k)
1880
+ };
1881
+ }
1882
+ _getOrderedFields(fields, layout) {
1883
+ const order = layout.fieldOrder;
1884
+ if (!order?.length) return fields;
1885
+ const fieldMap = new Map(fields.map((f) => [f.key, f]));
1886
+ const ordered = [];
1887
+ for (const key of order) {
1888
+ const f = fieldMap.get(key);
1889
+ if (f) {
1890
+ ordered.push(f);
1891
+ fieldMap.delete(key);
1892
+ }
1893
+ }
1894
+ for (const f of fieldMap.values()) {
1895
+ ordered.push(f);
1896
+ }
1897
+ return ordered;
1898
+ }
1899
+ _renderFormGrid(fields, columns, gap, ctx) {
1900
+ return import_lit2.html`
1901
+ <div class="form-grid" style="--dialog-form-columns:${columns}; gap:${gap}">
1902
+ ${fields.map((f) => this._renderFormField(f, ctx))}
1903
+ </div>
1904
+ `;
1905
+ }
1906
+ _renderGroupedForm(allFields, groups, layout, ctx) {
1907
+ const fieldMap = new Map(allFields.map((f) => [f.key, f]));
1908
+ const usedKeys = /* @__PURE__ */ new Set();
1909
+ const gap = layout.gap ?? "16px";
1910
+ const groupFragments = groups.map((group) => {
1911
+ const groupFields = [];
1912
+ for (const key of group.fields) {
1913
+ const f = fieldMap.get(key);
1914
+ if (f) {
1915
+ groupFields.push(f);
1916
+ usedKeys.add(key);
1917
+ }
1918
+ }
1919
+ if (!groupFields.length) return import_lit2.nothing;
1920
+ const cols = group.columns ?? layout.columns ?? 1;
1921
+ return import_lit2.html`
1922
+ <fieldset class="form-group">
1923
+ ${group.label ? import_lit2.html`<legend class="form-group-label">${group.label}</legend>` : import_lit2.nothing}
1924
+ ${this._renderFormGrid(groupFields, cols, gap, ctx)}
1925
+ </fieldset>
1926
+ `;
1927
+ });
1928
+ const remaining = allFields.filter((f) => !usedKeys.has(f.key));
1929
+ return import_lit2.html`
1930
+ ${groupFragments}
1931
+ ${remaining.length ? this._renderFormGrid(remaining, layout.columns ?? 1, gap, ctx) : import_lit2.nothing}
1932
+ `;
1933
+ }
1934
+ _renderForm(fields, layout, ctx) {
1935
+ const ordered = this._getOrderedFields(fields, layout);
1936
+ const gap = layout.gap ?? "16px";
1937
+ if (layout.groups?.length) {
1938
+ return this._renderGroupedForm(ordered, layout.groups, layout, ctx);
1939
+ }
1940
+ return this._renderFormGrid(ordered, layout.columns ?? 1, gap, ctx);
1941
+ }
1942
+ _renderFormField(field, ctx) {
1943
+ const value = ctx.getValue(field.key);
1944
+ const error = ctx.getError(field.key);
1945
+ const touched = ctx.getTouched(field.key);
1946
+ const showError = touched && !!error;
1947
+ if (field.inputType === "checkbox") {
1948
+ return import_lit2.html`
1949
+ <div class="form-field" data-type="checkbox" ?data-error=${showError}>
1950
+ <label class="form-checkbox-label">
1951
+ <input
1952
+ type="checkbox"
1953
+ class="form-checkbox"
1954
+ .checked=${!!value}
1955
+ @change=${(e) => ctx.onUpdate(field.key, e.target.checked)}
1956
+ @blur=${() => ctx.onBlur(field.key)}
1957
+ />
1958
+ <span class="form-checkbox-text">
1959
+ ${field.label}
1960
+ ${field.required ? import_lit2.html`<span class="form-required">*</span>` : import_lit2.nothing}
1961
+ </span>
1962
+ </label>
1963
+ ${showError ? import_lit2.html`<span class="form-error">${error}</span>` : import_lit2.nothing}
1964
+ </div>
1965
+ `;
1966
+ }
1967
+ return import_lit2.html`
1968
+ <div class="form-field" data-type=${field.inputType} ?data-error=${showError}>
1969
+ <label class="form-label" for="form-${field.key}">
1970
+ ${field.label} ${field.required ? import_lit2.html`<span class="form-required">*</span>` : import_lit2.nothing}
1971
+ </label>
1972
+ ${field.description ? import_lit2.html`<span class="form-hint">${field.description}</span>` : import_lit2.nothing}
1973
+ ${this._renderFormInput(field, value, ctx)}
1974
+ ${showError ? import_lit2.html`<span class="form-error">${error}</span>` : import_lit2.nothing}
1975
+ </div>
1976
+ `;
1977
+ }
1978
+ _renderFormInput(field, value, ctx) {
1979
+ switch (field.inputType) {
1980
+ case "select":
1981
+ return import_lit2.html`
1982
+ <select
1983
+ class="form-select"
1984
+ id="form-${field.key}"
1985
+ @change=${(e) => ctx.onUpdate(field.key, e.target.value)}
1986
+ @blur=${() => ctx.onBlur(field.key)}
1987
+ >
1988
+ <option value="" ?selected=${!value}>選択してください</option>
1989
+ ${field.options.map(
1990
+ (opt) => import_lit2.html`<option value=${opt} ?selected=${value === opt}>${opt}</option>`
1991
+ )}
1992
+ </select>
1993
+ `;
1994
+ case "number":
1995
+ return import_lit2.html`
1996
+ <input
1997
+ type="number"
1998
+ class="form-input"
1999
+ id="form-${field.key}"
2000
+ .value=${value != null ? String(value) : ""}
2001
+ min=${field.min ?? import_lit2.nothing}
2002
+ max=${field.max ?? import_lit2.nothing}
2003
+ placeholder=${field.placeholder || import_lit2.nothing}
2004
+ @input=${(e) => {
2005
+ const v = e.target.valueAsNumber;
2006
+ ctx.onUpdate(field.key, Number.isNaN(v) ? void 0 : v);
2007
+ }}
2008
+ @blur=${() => ctx.onBlur(field.key)}
2009
+ />
2010
+ `;
2011
+ case "date":
2012
+ return import_lit2.html`
2013
+ <input
2014
+ type="date"
2015
+ class="form-input"
2016
+ id="form-${field.key}"
2017
+ .value=${value != null ? String(value) : ""}
2018
+ @input=${(e) => {
2019
+ const str = e.target.value;
2020
+ ctx.onUpdate(field.key, str || void 0);
2021
+ }}
2022
+ @blur=${() => ctx.onBlur(field.key)}
2023
+ />
2024
+ `;
2025
+ default:
2026
+ return import_lit2.html`
2027
+ <input
2028
+ type=${field.inputType}
2029
+ class="form-input"
2030
+ id="form-${field.key}"
2031
+ .value=${value ?? ""}
2032
+ minlength=${field.minLength ?? import_lit2.nothing}
2033
+ maxlength=${field.maxLength ?? import_lit2.nothing}
2034
+ placeholder=${field.placeholder || import_lit2.nothing}
2035
+ @input=${(e) => ctx.onUpdate(field.key, e.target.value)}
2036
+ @blur=${() => ctx.onBlur(field.key)}
2037
+ />
2038
+ `;
2039
+ }
2040
+ }
1047
2041
  _renderButtons() {
1048
2042
  const s = this._state;
1049
2043
  if (!s.showConfirmButton && !s.showCancelButton) return import_lit2.nothing;
@@ -1058,6 +2052,39 @@ var OverlayDialog = class extends import_lit2.LitElement {
1058
2052
  </div>
1059
2053
  `;
1060
2054
  }
2055
+ // ─── Step Form Helpers ───────────────────────────────────
2056
+ _renderStepFormButtons() {
2057
+ const s = this._state;
2058
+ const isFirst = s.stepFormCurrentIndex === 0;
2059
+ const isLast = s.stepFormCurrentIndex === s.stepFormSteps.length - 1;
2060
+ const submitText = isLast ? s.stepFormSubmitText : s.stepFormNextText;
2061
+ return import_lit2.html`
2062
+ <div class="actions actions-step-form">
2063
+ <button class="btn btn-cancel" @click=${() => this.controller.onCancel()}>
2064
+ ${s.cancelButtonText}
2065
+ </button>
2066
+ <div class="step-form-nav">
2067
+ ${isFirst ? import_lit2.nothing : import_lit2.html`
2068
+ <button class="btn btn-prev" @click=${() => this.controller.onStepPrev()}>
2069
+ ${s.stepFormPrevText}
2070
+ </button>
2071
+ `}
2072
+ <button class="btn btn-confirm" @click=${() => this.controller.onStepNext()}>
2073
+ ${submitText}
2074
+ </button>
2075
+ </div>
2076
+ </div>
2077
+ `;
2078
+ }
2079
+ _deriveStepItems() {
2080
+ const s = this._state;
2081
+ return s.stepFormSteps.map((st, i) => {
2082
+ let status = "pending";
2083
+ if (i < s.stepFormCurrentIndex) status = "done";
2084
+ else if (i === s.stepFormCurrentIndex) status = "active";
2085
+ return { key: st.key, label: st.label, status };
2086
+ });
2087
+ }
1061
2088
  _renderBody() {
1062
2089
  const s = this._state;
1063
2090
  switch (s.dialogType) {
@@ -1084,18 +2111,51 @@ var OverlayDialog = class extends import_lit2.LitElement {
1084
2111
  ${s.label ? import_lit2.html`<p class="label">${s.label}</p>` : import_lit2.nothing}
1085
2112
  ${this._renderStepsList(s.steps)}
1086
2113
  `;
2114
+ case "form": {
2115
+ const ctx = this._createFormContext();
2116
+ return import_lit2.html`
2117
+ ${s.title ? import_lit2.html`<p class="label">${s.title}</p>` : import_lit2.nothing}
2118
+ ${s.description ? import_lit2.html`<p class="description">${s.description}</p>` : import_lit2.nothing}
2119
+ <div class="form-scroll-container">
2120
+ ${this._renderForm(s.formFields, s.formLayout, ctx)}
2121
+ </div>
2122
+ ${this._renderButtons()}
2123
+ `;
2124
+ }
2125
+ case "step-form": {
2126
+ const stepItems = this._deriveStepItems();
2127
+ const currentStep = s.stepFormSteps[s.stepFormCurrentIndex];
2128
+ if (!currentStep) return import_lit2.html`${import_lit2.nothing}`;
2129
+ const ctx = this._createStepFormContext();
2130
+ const stepCount = s.stepFormSteps.length;
2131
+ const counterText = `${s.stepFormCurrentIndex + 1} / ${stepCount}`;
2132
+ return import_lit2.html`
2133
+ ${this._renderStepsHeader(stepItems)}
2134
+ <p class="step-form-counter">${counterText}</p>
2135
+ <p class="label">${currentStep.label}</p>
2136
+ ${currentStep.description ? import_lit2.html`<p class="description">${currentStep.description}</p>` : import_lit2.nothing}
2137
+ ${currentStep.fields.length ? import_lit2.html`
2138
+ <div class="form-scroll-container">
2139
+ ${this._renderForm(currentStep.fields, currentStep.layout, ctx)}
2140
+ </div>
2141
+ ` : import_lit2.nothing}
2142
+ ${this._renderStepFormButtons()}
2143
+ `;
2144
+ }
1087
2145
  default:
1088
2146
  return import_lit2.html`${import_lit2.nothing}`;
1089
2147
  }
1090
2148
  }
1091
2149
  render() {
1092
2150
  const s = this._state;
1093
- const showHeaderTitle = s.title && s.dialogType !== "alert" && s.dialogType !== "confirm";
2151
+ const showHeaderTitle = s.title && s.dialogType !== "alert" && s.dialogType !== "confirm" && s.dialogType !== "form" && s.dialogType !== "step-form";
1094
2152
  return import_lit2.html`
1095
2153
  <div class="backdrop" ?data-open=${s.open} @click=${this._onBackdropClick}>
1096
- <div class="card">
2154
+ <div class="card" data-type=${s.dialogType} ?data-closing=${this._isClosing}>
1097
2155
  ${showHeaderTitle ? import_lit2.html`<p class="dialog-title">${s.title}</p>` : import_lit2.nothing}
1098
- <div class="card-body">${this._renderBody()}</div>
2156
+ <div class="card-body">
2157
+ ${(0, import_keyed.keyed)(this._bodyKey, import_lit2.html`<div class="body-inner">${this._renderBody()}</div>`)}
2158
+ </div>
1099
2159
  <div
1100
2160
  class="progress-bar"
1101
2161
  style="width:${s.progress ?? 0}%;opacity:${s.progress !== null ? 1 : 0}"
@@ -1112,6 +2172,12 @@ __decorateClass([
1112
2172
  __decorateClass([
1113
2173
  (0, import_decorators.state)()
1114
2174
  ], OverlayDialog.prototype, "_state", 2);
2175
+ __decorateClass([
2176
+ (0, import_decorators.state)()
2177
+ ], OverlayDialog.prototype, "_bodyKey", 2);
2178
+ __decorateClass([
2179
+ (0, import_decorators.state)()
2180
+ ], OverlayDialog.prototype, "_isClosing", 2);
1115
2181
  OverlayDialog = __decorateClass([
1116
2182
  (0, import_decorators.customElement)("overlay-dialog")
1117
2183
  ], OverlayDialog);
@@ -1141,6 +2207,28 @@ var DialogSingleton = class {
1141
2207
  __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
1142
2208
  return __privateGet(this, _controller).confirm(optionsOrLabel);
1143
2209
  }
2210
+ // ─── Form ──────────────────────────────────────────────────
2211
+ form(schema, options) {
2212
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
2213
+ return __privateGet(this, _controller).form(schema, options);
2214
+ }
2215
+ // ─── Step Form ─────────────────────────────────────────────
2216
+ showStepForm(steps, options) {
2217
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
2218
+ return __privateGet(this, _controller).showStepForm(steps, options);
2219
+ }
2220
+ onStepNext() {
2221
+ __privateGet(this, _controller).onStepNext();
2222
+ }
2223
+ onStepPrev() {
2224
+ __privateGet(this, _controller).onStepPrev();
2225
+ }
2226
+ updateStepFormField(fieldKey, value) {
2227
+ __privateGet(this, _controller).updateStepFormField(fieldKey, value);
2228
+ }
2229
+ touchStepFormField(fieldKey) {
2230
+ __privateGet(this, _controller).touchStepFormField(fieldKey);
2231
+ }
1144
2232
  // ─── Loading helpers ─────────────────────────────────────
1145
2233
  showLoading(label) {
1146
2234
  __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);