@konomi-app/ui 5.2.1 → 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) => {
@@ -318,6 +714,24 @@ var overlayStyles = css`
318
714
  --dialog-spinner-track: rgb(59 130 246 / 0.2);
319
715
  --dialog-spinner-arc: var(--dialog-primary);
320
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
+
321
735
  display: contents;
322
736
  font-family: var(--dialog-font-family);
323
737
  color: var(--dialog-text-color);
@@ -469,8 +883,6 @@ var overlayStyles = css`
469
883
  }
470
884
  }
471
885
 
472
- /* spin と spinner-enter は同じ transform を書き換えるため衝突する。
473
- ラッパーでスケール/フェードを担い、.spinner は回転専用にする。 */
474
886
  .spinner-wrap {
475
887
  font-size: var(--dialog-spinner-size);
476
888
  width: 1em;
@@ -953,6 +1365,204 @@ var overlayStyles = css`
953
1365
  background-color: #e5e7eb;
954
1366
  transition: background-color 400ms ease;
955
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
+ }
1565
+ }
956
1566
  `;
957
1567
 
958
1568
  // src/overlay-dialog.ts
@@ -976,6 +1586,7 @@ var OverlayDialog = class extends LitElement {
976
1586
  this._unsubscribe = this.controller.subscribe((s) => {
977
1587
  const wasOpen = this._state.open;
978
1588
  const prevDialogType = this._state.dialogType;
1589
+ const prevStepIndex = this._state.stepFormCurrentIndex;
979
1590
  if (s.open && !wasOpen) {
980
1591
  this._isClosing = false;
981
1592
  clearTimeout(this._closeTimer);
@@ -990,6 +1601,8 @@ var OverlayDialog = class extends LitElement {
990
1601
  }
991
1602
  if (s.open && s.dialogType !== prevDialogType) {
992
1603
  this._bodyKey++;
1604
+ } else if (s.open && s.dialogType === "step-form" && s.stepFormCurrentIndex !== prevStepIndex) {
1605
+ this._bodyKey++;
993
1606
  }
994
1607
  this._state = s;
995
1608
  this._syncBodyScroll(s.open);
@@ -1219,6 +1832,187 @@ var OverlayDialog = class extends LitElement {
1219
1832
  </ul>
1220
1833
  `;
1221
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
+ }
1222
2016
  _renderButtons() {
1223
2017
  const s = this._state;
1224
2018
  if (!s.showConfirmButton && !s.showCancelButton) return nothing;
@@ -1233,6 +2027,39 @@ var OverlayDialog = class extends LitElement {
1233
2027
  </div>
1234
2028
  `;
1235
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
+ }
1236
2063
  _renderBody() {
1237
2064
  const s = this._state;
1238
2065
  switch (s.dialogType) {
@@ -1259,16 +2086,47 @@ var OverlayDialog = class extends LitElement {
1259
2086
  ${s.label ? html`<p class="label">${s.label}</p>` : nothing}
1260
2087
  ${this._renderStepsList(s.steps)}
1261
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
+ }
1262
2120
  default:
1263
2121
  return html`${nothing}`;
1264
2122
  }
1265
2123
  }
1266
2124
  render() {
1267
2125
  const s = this._state;
1268
- 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";
1269
2127
  return html`
1270
2128
  <div class="backdrop" ?data-open=${s.open} @click=${this._onBackdropClick}>
1271
- <div class="card" ?data-closing=${this._isClosing}>
2129
+ <div class="card" data-type=${s.dialogType} ?data-closing=${this._isClosing}>
1272
2130
  ${showHeaderTitle ? html`<p class="dialog-title">${s.title}</p>` : nothing}
1273
2131
  <div class="card-body">
1274
2132
  ${keyed(this._bodyKey, html`<div class="body-inner">${this._renderBody()}</div>`)}
@@ -1324,6 +2182,28 @@ var DialogSingleton = class {
1324
2182
  __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
1325
2183
  return __privateGet(this, _controller).confirm(optionsOrLabel);
1326
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
+ }
1327
2207
  // ─── Loading helpers ─────────────────────────────────────
1328
2208
  showLoading(label) {
1329
2209
  __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);