@shwfed/nuxt 0.11.31 → 0.11.32

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.
@@ -57,6 +57,9 @@ function cloneConfig(config2) {
57
57
  if (config2.orientation) {
58
58
  nextConfig.orientation = config2.orientation;
59
59
  }
60
+ if (config2.bordered) {
61
+ nextConfig.bordered = config2.bordered;
62
+ }
60
63
  if (config2.style) {
61
64
  nextConfig.style = config2.style;
62
65
  }
@@ -65,6 +68,12 @@ function cloneConfig(config2) {
65
68
  function getConfigOrientation(config2) {
66
69
  return config2.orientation ?? "horizontal";
67
70
  }
71
+ function usesContentsOrientation(config2) {
72
+ return getConfigOrientation(config2) === "contents";
73
+ }
74
+ function isConfigBordered(config2) {
75
+ return usesContentsOrientation(config2) && config2.bordered === true;
76
+ }
68
77
  function tryEvaluateExpression(source, context) {
69
78
  try {
70
79
  return {
@@ -100,11 +109,11 @@ function getConfigStyle(config2) {
100
109
  }
101
110
  return style;
102
111
  }
103
- function getFieldStyle(field) {
104
- if (!field.style) {
112
+ function getFieldPartStyle(styleExpression) {
113
+ if (!styleExpression) {
105
114
  return {};
106
115
  }
107
- const style = evaluateExpression(field.style, void 0, {});
116
+ const style = evaluateExpression(styleExpression, void 0, {});
108
117
  const normalizedStyle = {};
109
118
  if (!style || typeof style !== "object" || Array.isArray(style)) {
110
119
  return normalizedStyle;
@@ -116,6 +125,27 @@ function getFieldStyle(field) {
116
125
  }
117
126
  return normalizedStyle;
118
127
  }
128
+ function getFieldStyle(field) {
129
+ return getFieldPartStyle(field.style);
130
+ }
131
+ function getFieldContainerStyle(field, config2) {
132
+ if (!isPassiveField(field) && usesContentsOrientation(config2)) {
133
+ return {};
134
+ }
135
+ return getFieldStyle(field);
136
+ }
137
+ function getFieldLabelStyle(field, config2) {
138
+ if (!usesContentsOrientation(config2)) {
139
+ return {};
140
+ }
141
+ return getFieldPartStyle(field.labelStyle);
142
+ }
143
+ function getFieldContentStyle(field, config2) {
144
+ if (!usesContentsOrientation(config2)) {
145
+ return {};
146
+ }
147
+ return getFieldPartStyle(field.contentStyle);
148
+ }
119
149
  function isPassiveField(field) {
120
150
  return field.type === "slot" || field.type === "empty";
121
151
  }
@@ -158,6 +188,115 @@ function initializeFieldValues(config2) {
158
188
  );
159
189
  }
160
190
  }
191
+ const MIME_LABELS = {
192
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Excel",
193
+ "application/vnd.ms-excel": "Excel",
194
+ "application/x-zip-compressed": "ZIP",
195
+ "application/zip": "ZIP",
196
+ "application/pdf": "PDF",
197
+ "application/ofd": "OFD",
198
+ "application/xml": "XML",
199
+ "image/png": "\u56FE\u7247",
200
+ "image/jpg": "\u56FE\u7247",
201
+ "image/jpeg": "\u56FE\u7247",
202
+ "application/msword": "Word",
203
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Word"
204
+ };
205
+ const FILE_EXTENSION_ICONS = {
206
+ xlsx: "vscode-icons:file-type-excel",
207
+ xls: "vscode-icons:file-type-excel",
208
+ csv: "vscode-icons:file-type-excel",
209
+ pdf: "vscode-icons:file-type-pdf2",
210
+ doc: "vscode-icons:file-type-word",
211
+ docx: "vscode-icons:file-type-word",
212
+ ppt: "vscode-icons:file-type-powerpoint",
213
+ pptx: "vscode-icons:file-type-powerpoint",
214
+ png: "vscode-icons:file-type-image",
215
+ jpg: "vscode-icons:file-type-image",
216
+ jpeg: "vscode-icons:file-type-image",
217
+ gif: "vscode-icons:file-type-image",
218
+ webp: "vscode-icons:file-type-image",
219
+ svg: "vscode-icons:file-type-svg",
220
+ txt: "vscode-icons:file-type-text",
221
+ json: "vscode-icons:file-type-json",
222
+ zip: "vscode-icons:file-type-zip"
223
+ };
224
+ function getFileIcon(filename) {
225
+ const ext = filename.split(".").pop()?.toLowerCase() ?? "";
226
+ return FILE_EXTENSION_ICONS[ext] ?? "vscode-icons:default-file";
227
+ }
228
+ function getUploadAcceptString(field) {
229
+ if (!field.accept || field.accept.length === 0) {
230
+ return void 0;
231
+ }
232
+ return field.accept.join(",");
233
+ }
234
+ function getUploadDescription(field) {
235
+ if (!field.accept || field.accept.length === 0) {
236
+ return "";
237
+ }
238
+ const uniqueLabels = [...new Set(field.accept.map((mime) => MIME_LABELS[mime] ?? mime))];
239
+ return t("upload-accept-description", { formats: uniqueLabels.join(` ${t("upload-accept-or")} `) });
240
+ }
241
+ function getUploadMaxCount(field) {
242
+ if (!field.maxCount) {
243
+ return Infinity;
244
+ }
245
+ const result = evaluateExpression(field.maxCount, { form: modelValue.value }, Infinity);
246
+ if (typeof result === "bigint") {
247
+ return Number(result);
248
+ }
249
+ return result;
250
+ }
251
+ function getUploadFiles(field) {
252
+ const raw = getProperty(modelValue.value, field.path);
253
+ if (!Array.isArray(raw)) {
254
+ return [];
255
+ }
256
+ return raw.filter((item) => item instanceof File);
257
+ }
258
+ function handleUploadInputChange(field, event) {
259
+ const target = event.target;
260
+ if (!(target instanceof HTMLInputElement)) {
261
+ return;
262
+ }
263
+ handleUploadFiles(field, target.files);
264
+ target.value = "";
265
+ }
266
+ function handleUploadFiles(field, fileList) {
267
+ if (!fileList || fileList.length === 0) {
268
+ return;
269
+ }
270
+ const existing = getUploadFiles(field);
271
+ const maxCount = getUploadMaxCount(field);
272
+ const remaining = maxCount === Infinity ? fileList.length : Math.max(0, maxCount - existing.length);
273
+ const incoming = Array.from(fileList).slice(0, remaining);
274
+ const next = [...existing, ...incoming];
275
+ if (next.length === 0) {
276
+ deleteProperty(modelValue.value, field.path);
277
+ } else {
278
+ setProperty(modelValue.value, field.path, next);
279
+ }
280
+ }
281
+ function removeUploadFile(field, index) {
282
+ const files = getUploadFiles(field);
283
+ const next = files.filter((_, i) => i !== index);
284
+ if (next.length === 0) {
285
+ deleteProperty(modelValue.value, field.path);
286
+ } else {
287
+ setProperty(modelValue.value, field.path, next);
288
+ }
289
+ }
290
+ function handleUploadDrop(field, event) {
291
+ event.preventDefault();
292
+ if (isFieldDisabled(field)) {
293
+ return;
294
+ }
295
+ handleUploadFiles(field, event.dataTransfer?.files ?? null);
296
+ }
297
+ function handleUploadDragOver(event) {
298
+ event.preventDefault();
299
+ }
161
300
  function stringifySelectValue(value) {
162
301
  try {
163
302
  return JSON.stringify(value);
@@ -437,6 +576,7 @@ export {
437
576
  SlotFieldC,
438
577
  SUPPORTED_COMPATIBILITY_DATES,
439
578
  StringFieldC,
579
+ UploadFieldC,
440
580
  ValidationRuleC,
441
581
  createFieldsConfig,
442
582
  validationC
@@ -447,7 +587,8 @@ export {
447
587
  <div
448
588
  :class="[
449
589
  'relative p-1 -m-1 border border-dashed',
450
- isCheating ? 'border-(--primary)/20 rounded hover:border-(--primary)/40 transition-colors duration-150 group cursor-pointer' : 'border-transparent'
590
+ isCheating ? 'border-(--primary)/20 rounded hover:border-(--primary)/40 transition-colors duration-150 group cursor-pointer' : 'border-transparent',
591
+ isConfigBordered(displayConfig) ? '!p-0 !m-0 border-solid border-zinc-200' : ''
451
592
  ]"
452
593
  :style="getConfigStyle(displayConfig)"
453
594
  >
@@ -479,8 +620,20 @@ export {
479
620
  v-for="field in displayConfig.fields"
480
621
  :key="field.id"
481
622
  >
623
+ <div
624
+ v-if="field.type === 'slot' && isConfigBordered(displayConfig)"
625
+ :class="'border-b border-r border-zinc-200'"
626
+ :style="getFieldStyle(field)"
627
+ >
628
+ <slot
629
+ :name="field.id"
630
+ :form="slotForm"
631
+ :style="{}"
632
+ :valid="valid"
633
+ />
634
+ </div>
482
635
  <slot
483
- v-if="field.type === 'slot'"
636
+ v-else-if="field.type === 'slot'"
484
637
  :name="field.id"
485
638
  :form="slotForm"
486
639
  :style="getFieldStyle(field)"
@@ -488,16 +641,100 @@ export {
488
641
  />
489
642
  <div
490
643
  v-else-if="field.type === 'empty'"
644
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
491
645
  :style="getFieldStyle(field)"
492
646
  />
647
+ <div
648
+ v-else-if="field.type === 'upload' && !isFieldHidden(field)"
649
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
650
+ :style="getFieldContainerStyle(field, displayConfig)"
651
+ class="flex flex-col gap-2 p-2"
652
+ >
653
+ <p class="text-sm font-medium text-zinc-700">
654
+ <span class="inline-flex items-start gap-0.5">
655
+ <span>{{ getFieldLabel(field) }}</span>
656
+ <sup
657
+ v-if="isFieldRequired(field)"
658
+ class="text-red-500 leading-none"
659
+ >*</sup>
660
+ </span>
661
+ </p>
662
+ <label
663
+ v-if="getUploadFiles(field).length < getUploadMaxCount(field)"
664
+ :class="[
665
+ 'flex cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed px-4 py-8 transition-colors',
666
+ isFieldDisabled(field) ? 'cursor-not-allowed border-zinc-200 opacity-50' : 'border-zinc-300 hover:border-zinc-400 hover:bg-zinc-50'
667
+ ]"
668
+ @drop="handleUploadDrop(field, $event)"
669
+ @dragover="handleUploadDragOver"
670
+ >
671
+ <Icon
672
+ :icon="field.icon ?? 'fluent:cloud-arrow-up-20-regular'"
673
+ class="text-4xl text-zinc-400"
674
+ />
675
+ <p
676
+ v-if="field.description"
677
+ class="text-sm text-zinc-600"
678
+ v-html="$md.inline`${getLocalizedText(field.description, locale) ?? ''}`()"
679
+ />
680
+ <p
681
+ v-else
682
+ class="text-sm text-zinc-600"
683
+ >
684
+ {{ t("upload-click-or-drag") }}
685
+ </p>
686
+ <p class="text-xs text-zinc-400">
687
+ {{ getUploadDescription(field) || t("upload-accept-all") }}
688
+ </p>
689
+ <input
690
+ type="file"
691
+ class="sr-only"
692
+ :accept="getUploadAcceptString(field)"
693
+ :disabled="isFieldDisabled(field)"
694
+ :multiple="getUploadMaxCount(field) !== 1"
695
+ @change="handleUploadInputChange(field, $event)"
696
+ >
697
+ </label>
698
+ <ul
699
+ v-if="getUploadFiles(field).length > 0"
700
+ class="flex flex-col gap-1"
701
+ >
702
+ <li
703
+ v-for="(file, fileIndex) in getUploadFiles(field)"
704
+ :key="`${field.id}:${fileIndex}:${file.name}`"
705
+ class="flex items-center gap-2 rounded-md border border-zinc-200 px-3 py-2"
706
+ >
707
+ <Icon
708
+ :icon="getFileIcon(file.name)"
709
+ class="shrink-0 text-lg"
710
+ />
711
+ <span class="flex-1 truncate text-sm text-zinc-700">{{ file.name }}</span>
712
+ <button
713
+ type="button"
714
+ class="shrink-0 text-zinc-300 transition-colors hover:text-red-500"
715
+ :disabled="isFieldDisabled(field)"
716
+ @click="removeUploadFile(field, fileIndex)"
717
+ >
718
+ <Icon
719
+ icon="fluent:delete-20-regular"
720
+ class="text-lg"
721
+ />
722
+ </button>
723
+ </li>
724
+ </ul>
725
+ </div>
493
726
  <Field
494
727
  v-else-if="!isFieldHidden(field)"
495
728
  :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
496
729
  :data-invalid="isFieldInvalid(field) ? 'true' : void 0"
497
730
  :orientation="getConfigOrientation(displayConfig)"
498
- :style="getFieldStyle(field)"
731
+ :style="getFieldContainerStyle(field, displayConfig)"
499
732
  >
500
- <FieldLabel :for="['string', 'textarea', 'number', 'select'].includes(field.type) ? `${id}:${field.path}` : void 0">
733
+ <FieldLabel
734
+ :for="['string', 'textarea', 'number', 'select'].includes(field.type) ? `${id}:${field.path}` : void 0"
735
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
736
+ :style="getFieldLabelStyle(field, displayConfig)"
737
+ >
501
738
  <span class="inline-flex items-start gap-0.5">
502
739
  <span>{{ getFieldLabel(field) }}</span>
503
740
  <sup
@@ -509,7 +746,10 @@ export {
509
746
  <span class="font-mono">{{ field.path }}</span>
510
747
  </span>
511
748
  </FieldLabel>
512
- <FieldContent>
749
+ <FieldContent
750
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200 p-2' : void 0"
751
+ :style="getFieldContentStyle(field, displayConfig)"
752
+ >
513
753
  <Popover
514
754
  v-if="field.type === 'calendar'"
515
755
  @update:open="(open) => handleCalendarOpenChange(field, open)"
@@ -809,7 +1049,10 @@ export {
809
1049
  </InputGroup>
810
1050
  </template>
811
1051
 
812
- <FieldError v-if="isFieldInvalid(field)">
1052
+ <FieldError
1053
+ v-if="isFieldInvalid(field)"
1054
+ :class="usesContentsOrientation(displayConfig) ? 'static pt-1' : void 0"
1055
+ >
813
1056
  <span v-html="renderValidationMessage(field)" />
814
1057
  </FieldError>
815
1058
  </FieldContent>
@@ -826,19 +1069,31 @@ export {
826
1069
  "clear": "清空",
827
1070
  "select-empty": "无搜索结果",
828
1071
  "select-search-placeholder": "搜索…",
829
- "select-placeholder": "选择…"
1072
+ "select-placeholder": "选择…",
1073
+ "upload-click-or-drag": "点击或拖拽文件到此处上传",
1074
+ "upload-accept-description": "仅接受 {formats} 格式文件",
1075
+ "upload-accept-or": "或",
1076
+ "upload-accept-all": "接受所有格式文件"
830
1077
  },
831
1078
  "ja": {
832
1079
  "clear": "クリア",
833
1080
  "select-empty": "結果はありません",
834
1081
  "select-search-placeholder": "検索…",
835
- "select-placeholder": "選択…"
1082
+ "select-placeholder": "選択…",
1083
+ "upload-click-or-drag": "クリックまたはファイルをここにドラッグしてアップロード",
1084
+ "upload-accept-description": "{formats} 形式のファイルのみ受け付けます",
1085
+ "upload-accept-or": "または",
1086
+ "upload-accept-all": "すべての形式を受け付けます"
836
1087
  },
837
1088
  "en": {
838
1089
  "clear": "Clear",
839
1090
  "select-empty": "No results",
840
1091
  "select-search-placeholder": "Search…",
841
- "select-placeholder": "Select…"
1092
+ "select-placeholder": "Select…",
1093
+ "upload-click-or-drag": "Click or drag files here to upload",
1094
+ "upload-accept-description": "Only {formats} format files accepted",
1095
+ "upload-accept-or": "or",
1096
+ "upload-accept-all": "All formats accepted"
842
1097
  }
843
1098
  }
844
1099
  </i18n>