@shwfed/nuxt 0.7.10 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +2 -1
  3. package/dist/runtime/components/table.d.vue.ts +68 -2
  4. package/dist/runtime/components/table.vue +0 -1
  5. package/dist/runtime/components/table.vue.d.ts +68 -2
  6. package/dist/runtime/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
  7. package/dist/runtime/components/ui/field/FieldLabel.vue +1 -1
  8. package/dist/runtime/components/ui/fields/Fields.vue +14 -6
  9. package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue +28 -14
  10. package/dist/runtime/components/ui/icon-picker/IconPicker.d.vue.ts +15 -0
  11. package/dist/runtime/components/ui/icon-picker/IconPicker.vue +178 -0
  12. package/dist/runtime/components/ui/icon-picker/IconPicker.vue.d.ts +15 -0
  13. package/dist/runtime/components/ui/icon-picker/index.d.ts +1 -0
  14. package/dist/runtime/components/ui/icon-picker/index.js +1 -0
  15. package/dist/runtime/components/ui/input-group/InputGroupComboboxInput.vue +1 -1
  16. package/dist/runtime/components/ui/input-group/InputGroupInput.vue +1 -1
  17. package/dist/runtime/components/ui/input-group/InputGroupNumberField.vue +1 -1
  18. package/dist/runtime/components/ui/input-group/InputGroupTextarea.vue +1 -1
  19. package/dist/runtime/components/ui/native-select/NativeSelect.d.vue.ts +2 -2
  20. package/dist/runtime/components/ui/native-select/NativeSelect.vue +1 -1
  21. package/dist/runtime/components/ui/native-select/NativeSelect.vue.d.ts +2 -2
  22. package/dist/runtime/components/ui/switch/Switch.vue +2 -2
  23. package/dist/runtime/components/ui/table/Table.d.vue.ts +69 -3
  24. package/dist/runtime/components/ui/table/Table.vue +201 -41
  25. package/dist/runtime/components/ui/table/Table.vue.d.ts +69 -3
  26. package/dist/runtime/components/ui/table/schema.d.ts +107 -4
  27. package/dist/runtime/components/ui/table/schema.js +106 -90
  28. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.d.vue.ts +68 -2
  29. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.vue +732 -257
  30. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.vue.d.ts +68 -2
  31. package/dist/runtime/components/ui/textarea/Textarea.vue +1 -1
  32. package/dist/runtime/layouts/default.d.vue.ts +40 -0
  33. package/dist/runtime/layouts/default.vue +19 -0
  34. package/dist/runtime/layouts/default.vue.d.ts +40 -0
  35. package/dist/runtime/plugins/toast/index.d.ts +2 -2
  36. package/dist/runtime/table-renderers/builtins.js +151 -75
  37. package/dist/runtime/table-renderers/registry.d.ts +1 -1
  38. package/dist/runtime/utils/coders.d.ts +2 -0
  39. package/dist/runtime/utils/coders.js +13 -0
  40. package/package.json +6 -6
@@ -6,7 +6,7 @@ import z from "zod";
6
6
  import { computed, nextTick, ref, watch } from "vue";
7
7
  import { useI18n } from "vue-i18n";
8
8
  import { useTableRenderers } from "../../../composables/useTableRenderers";
9
- import { dotPropC, expressionC } from "../../../utils/coders";
9
+ import { dotPropC, expressionC, getLocalizedText, hasVisibleLocaleValue } from "../../../utils/coders";
10
10
  import { cn } from "../../../utils/cn";
11
11
  import ExpressionEditor from "../expression-editor/ExpressionEditor.vue";
12
12
  import { Button } from "../button";
@@ -20,7 +20,7 @@ import {
20
20
  DialogTitle
21
21
  } from "../dialog";
22
22
  import { Input } from "../input";
23
- import { InputGroup, InputGroupNumberField } from "../input-group";
23
+ import { InputGroup, InputGroupAddon, InputGroupNumberField } from "../input-group";
24
24
  import Locale from "../locale/Locale.vue";
25
25
  import { NativeSelect, NativeSelectOption } from "../native-select";
26
26
  import { NumberField, NumberFieldInput } from "../number-field";
@@ -30,7 +30,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../tabs";
30
30
  import { Toggle } from "../toggle";
31
31
  import {
32
32
  getColumnTechnicalKey,
33
- getLocalizedText,
33
+ normalizePaginationPageSizes,
34
+ resolvePaginationPageSize,
34
35
  TableConfigC,
35
36
  TableConfigCellStylesC,
36
37
  TableConfigEnableMultiRowSelectionC,
@@ -57,6 +58,7 @@ const GENERATED_COLUMN_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab]
57
58
  const TableConfiguratorColumnAccessorReadC = expressionC(/.+/, { row: "dyn", index: "int" });
58
59
  const TableConfiguratorColumnAccessorWriteC = z.string().trim().refine((value) => value.length > 0, { error: "\u5199\u5165\u8868\u8FBE\u5F0F\u4E0D\u80FD\u4E3A\u7A7A" });
59
60
  const TOOLTIP_EXPRESSION_EXAMPLE = "{{ expression }}";
61
+ const TABLE_CONFIGURATOR_INITIAL_STATE_SORTABLE_GROUP = "table-configurator-initial-state";
60
62
  const props = defineProps({
61
63
  config: { type: Object, required: true }
62
64
  });
@@ -66,7 +68,7 @@ const open = defineModel("open", { type: Boolean, ...{
66
68
  } });
67
69
  const { $toast } = useNuxtApp();
68
70
  const { t, locale } = useI18n();
69
- const { listTableRenderers, resolveTableRenderer } = useTableRenderers();
71
+ const { getTableRenderer, listTableRenderers, resolveTableRenderer } = useTableRenderers();
70
72
  const search = ref("");
71
73
  const childSearch = ref("");
72
74
  const selectedItemId = ref("general");
@@ -80,6 +82,7 @@ const draftCellStyles = ref();
80
82
  const draftPaginationLeft = ref();
81
83
  const draftPaginationRight = ref();
82
84
  const draftPageSize = ref();
85
+ const draftPaginationPageSizes = ref([]);
83
86
  const leftPinnedSortableListRef = ref(null);
84
87
  const leftPinnedSortableItemIds = ref([]);
85
88
  const centerSortableListRef = ref(null);
@@ -89,6 +92,8 @@ const rightPinnedSortableItemIds = ref([]);
89
92
  const leftPinnedRootItemIds = ref([]);
90
93
  const centerRootItemIds = ref([]);
91
94
  const rightPinnedRootItemIds = ref([]);
95
+ const paginationPageSizesSortableListRef = ref(null);
96
+ const paginationPageSizesSortableItemIds = ref([]);
92
97
  const selectedChildSortableListRef = ref(null);
93
98
  const selectedChildSortableItemIds = ref([]);
94
99
  const getRowIdEditor = ref(null);
@@ -96,7 +101,6 @@ const getSubRowsEditor = ref(null);
96
101
  const enableRowSelectionEditor = ref(null);
97
102
  const enableMultiRowSelectionEditor = ref(null);
98
103
  const cellStylesEditor = ref(null);
99
- const importInputRef = ref(null);
100
104
  const accessorPathEditor = ref(null);
101
105
  const accessorReadEditor = ref(null);
102
106
  const accessorWriteEditor = ref(null);
@@ -367,6 +371,18 @@ function normalizeOptionalNumber(value) {
367
371
  function getInitialPageSize(config) {
368
372
  return config.props?.initialState?.pagination?.pageSize;
369
373
  }
374
+ function getInitialPaginationPageSizes(config) {
375
+ return normalizePaginationPageSizes(config.paginationPageSizes) ?? [];
376
+ }
377
+ function createPaginationPageSizeItem(value = void 0) {
378
+ return {
379
+ itemId: crypto.randomUUID(),
380
+ value
381
+ };
382
+ }
383
+ function createPaginationPageSizeItems(values) {
384
+ return (normalizePaginationPageSizes(values) ?? []).map((value) => createPaginationPageSizeItem(value));
385
+ }
370
386
  function isRendererConfigExposed(value) {
371
387
  if (typeof value !== "object" || value === null) {
372
388
  return false;
@@ -482,6 +498,35 @@ function reorderItemIds(itemIds, oldIndex, newIndex) {
482
498
  nextItemIds.splice(newIndex, 0, movedItemId);
483
499
  return nextItemIds;
484
500
  }
501
+ function clampInsertionIndex(index, itemCount) {
502
+ return Math.min(Math.max(index, 0), itemCount);
503
+ }
504
+ function areItemIdsEqual(left, right) {
505
+ if (left.length !== right.length) {
506
+ return false;
507
+ }
508
+ return left.every((itemId, index) => itemId === right[index]);
509
+ }
510
+ function syncDraftColumnTreeRootOrder() {
511
+ const existingRootItemIds = draftColumnTree.value.rootItemIds;
512
+ const nextRootItemIds = [
513
+ ...leftPinnedRootItemIds.value,
514
+ ...centerRootItemIds.value,
515
+ ...rightPinnedRootItemIds.value
516
+ ].filter((itemId, index, itemIds) => existingRootItemIds.includes(itemId) && itemIds.indexOf(itemId) === index);
517
+ for (const itemId of existingRootItemIds) {
518
+ if (!nextRootItemIds.includes(itemId)) {
519
+ nextRootItemIds.push(itemId);
520
+ }
521
+ }
522
+ if (areItemIdsEqual(existingRootItemIds, nextRootItemIds)) {
523
+ return;
524
+ }
525
+ draftColumnTree.value = {
526
+ ...draftColumnTree.value,
527
+ rootItemIds: nextRootItemIds
528
+ };
529
+ }
485
530
  function initializeInitialStateGroups(config) {
486
531
  const leafColumnIdToRootItemId = /* @__PURE__ */ new Map();
487
532
  for (const item of rootInitialStateItems.value) {
@@ -530,6 +575,7 @@ function initializeInitialStateGroups(config) {
530
575
  rightPinnedRootItemIds.value = rightItemIds;
531
576
  }
532
577
  function resetDraftState(config) {
578
+ const nextPaginationPageSizes = getInitialPaginationPageSizes(config);
533
579
  draftBaseConfig.value = config;
534
580
  draftColumnTree.value = buildTableConfiguratorColumnTree(config.columns);
535
581
  draftGetRowId.value = config.getRowId;
@@ -539,8 +585,10 @@ function resetDraftState(config) {
539
585
  draftCellStyles.value = config.cellStyles;
540
586
  draftPaginationLeft.value = config.paginationLeft;
541
587
  draftPaginationRight.value = config.paginationRight;
542
- draftPageSize.value = getInitialPageSize(config);
588
+ draftPageSize.value = resolvePaginationPageSize(getInitialPageSize(config), nextPaginationPageSizes);
589
+ draftPaginationPageSizes.value = createPaginationPageSizeItems(nextPaginationPageSizes);
543
590
  initializeInitialStateGroups(config);
591
+ syncDraftColumnTreeRootOrder();
544
592
  childSearch.value = "";
545
593
  selectedItemId.value = "general";
546
594
  clearExpressionValidation();
@@ -553,11 +601,13 @@ watch(() => props.config, (config) => {
553
601
  const leftPinnedSortable = useSortable(leftPinnedSortableListRef, leftPinnedSortableItemIds);
554
602
  const centerSortable = useSortable(centerSortableListRef, centerSortableItemIds);
555
603
  const rightPinnedSortable = useSortable(rightPinnedSortableListRef, rightPinnedSortableItemIds);
604
+ const paginationPageSizesSortable = useSortable(paginationPageSizesSortableListRef, paginationPageSizesSortableItemIds);
556
605
  const childSortable = useSortable(selectedChildSortableListRef, selectedChildSortableItemIds);
557
606
  function syncSortableItemIds() {
558
607
  leftPinnedSortableItemIds.value = leftPinnedRootItemIds.value.slice();
559
608
  centerSortableItemIds.value = centerRootItemIds.value.slice();
560
609
  rightPinnedSortableItemIds.value = rightPinnedRootItemIds.value.slice();
610
+ paginationPageSizesSortableItemIds.value = draftPaginationPageSizes.value.map((item) => item.itemId);
561
611
  selectedChildSortableItemIds.value = selectedColumnNode.value?.childItemIds.slice() ?? [];
562
612
  }
563
613
  function normalizeInitialStateGroups() {
@@ -596,17 +646,87 @@ function normalizeInitialStateGroups() {
596
646
  centerRootItemIds.value = centerItemIds;
597
647
  rightPinnedRootItemIds.value = rightItemIds;
598
648
  }
649
+ function resolveInitialStateGroupByElement(element) {
650
+ if (!element) {
651
+ return void 0;
652
+ }
653
+ if (element === leftPinnedSortableListRef.value) {
654
+ return "left";
655
+ }
656
+ if (element === centerSortableListRef.value) {
657
+ return "center";
658
+ }
659
+ if (element === rightPinnedSortableListRef.value) {
660
+ return "right";
661
+ }
662
+ return void 0;
663
+ }
664
+ function getInitialStateGroupItemIds(group) {
665
+ if (group === "left") {
666
+ return leftPinnedRootItemIds.value.slice();
667
+ }
668
+ if (group === "center") {
669
+ return centerRootItemIds.value.slice();
670
+ }
671
+ return rightPinnedRootItemIds.value.slice();
672
+ }
673
+ function setInitialStateGroupItemIds(group, itemIds) {
674
+ if (group === "left") {
675
+ leftPinnedRootItemIds.value = itemIds;
676
+ return;
677
+ }
678
+ if (group === "center") {
679
+ centerRootItemIds.value = itemIds;
680
+ return;
681
+ }
682
+ rightPinnedRootItemIds.value = itemIds;
683
+ }
684
+ function reorderInitialStateGroup(group, oldIndex, newIndex) {
685
+ setInitialStateGroupItemIds(
686
+ group,
687
+ reorderItemIds(getInitialStateGroupItemIds(group), oldIndex, newIndex)
688
+ );
689
+ }
690
+ function canMoveInitialStateItemToGroup(itemId, group) {
691
+ if (group === "center") {
692
+ return true;
693
+ }
694
+ return rootInitialStateItems.value.some((item) => item.itemId === itemId && item.canManage);
695
+ }
696
+ function moveInitialStateItemBetweenGroups(fromGroup, toGroup, oldIndex, newIndex) {
697
+ if (fromGroup === toGroup) {
698
+ reorderInitialStateGroup(toGroup, oldIndex, newIndex);
699
+ return;
700
+ }
701
+ const sourceItemIds = getInitialStateGroupItemIds(fromGroup);
702
+ const targetItemIds = getInitialStateGroupItemIds(toGroup);
703
+ const movedItemId = sourceItemIds[oldIndex];
704
+ if (!movedItemId || !canMoveInitialStateItemToGroup(movedItemId, toGroup)) {
705
+ return;
706
+ }
707
+ const nextSourceItemIds = sourceItemIds.filter((_, index) => index !== oldIndex);
708
+ const nextTargetItemIds = targetItemIds.slice();
709
+ nextTargetItemIds.splice(clampInsertionIndex(newIndex, nextTargetItemIds.length), 0, movedItemId);
710
+ setInitialStateGroupItemIds(fromGroup, nextSourceItemIds);
711
+ setInitialStateGroupItemIds(toGroup, nextTargetItemIds);
712
+ }
713
+ function handleInitialStateSortableAdd(event) {
714
+ const oldIndex = event.oldIndex;
715
+ const newIndex = event.newIndex;
716
+ const fromGroup = resolveInitialStateGroupByElement(event.from);
717
+ const toGroup = resolveInitialStateGroupByElement(event.to);
718
+ if (selectedItemId.value !== "general" || oldIndex === void 0 || newIndex === void 0 || !fromGroup || !toGroup) {
719
+ return;
720
+ }
721
+ moveInitialStateItemBetweenGroups(fromGroup, toGroup, oldIndex, newIndex);
722
+ }
599
723
  function handleLeftPinnedSortableUpdate(event) {
600
724
  const oldIndex = event.oldIndex;
601
725
  const newIndex = event.newIndex;
602
726
  if (selectedItemId.value !== "general" || oldIndex === void 0 || newIndex === void 0 || oldIndex === newIndex) {
603
727
  return;
604
728
  }
605
- leftPinnedRootItemIds.value = reorderItemIds(
606
- leftPinnedRootItemIds.value,
607
- oldIndex,
608
- newIndex
609
- );
729
+ reorderInitialStateGroup("left", oldIndex, newIndex);
610
730
  }
611
731
  function handleCenterSortableUpdate(event) {
612
732
  const oldIndex = event.oldIndex;
@@ -614,11 +734,7 @@ function handleCenterSortableUpdate(event) {
614
734
  if (selectedItemId.value !== "general" || oldIndex === void 0 || newIndex === void 0 || oldIndex === newIndex) {
615
735
  return;
616
736
  }
617
- centerRootItemIds.value = reorderItemIds(
618
- centerRootItemIds.value,
619
- oldIndex,
620
- newIndex
621
- );
737
+ reorderInitialStateGroup("center", oldIndex, newIndex);
622
738
  }
623
739
  function handleRightPinnedSortableUpdate(event) {
624
740
  const oldIndex = event.oldIndex;
@@ -626,11 +742,24 @@ function handleRightPinnedSortableUpdate(event) {
626
742
  if (selectedItemId.value !== "general" || oldIndex === void 0 || newIndex === void 0 || oldIndex === newIndex) {
627
743
  return;
628
744
  }
629
- rightPinnedRootItemIds.value = reorderItemIds(
630
- rightPinnedRootItemIds.value,
745
+ reorderInitialStateGroup("right", oldIndex, newIndex);
746
+ }
747
+ function handlePaginationPageSizesSortableUpdate(event) {
748
+ const oldIndex = event.oldIndex;
749
+ const newIndex = event.newIndex;
750
+ if (selectedItemId.value !== "general" || oldIndex === void 0 || newIndex === void 0 || oldIndex === newIndex) {
751
+ return;
752
+ }
753
+ const nextItemIds = reorderItemIds(
754
+ draftPaginationPageSizes.value.map((item) => item.itemId),
631
755
  oldIndex,
632
756
  newIndex
633
757
  );
758
+ const itemMap = new Map(draftPaginationPageSizes.value.map((item) => [item.itemId, item]));
759
+ draftPaginationPageSizes.value = nextItemIds.flatMap((itemId) => {
760
+ const item = itemMap.get(itemId);
761
+ return item ? [item] : [];
762
+ });
634
763
  }
635
764
  function handleChildSortableUpdate(event) {
636
765
  const oldIndex = event.oldIndex;
@@ -648,18 +777,26 @@ function handleChildSortableUpdate(event) {
648
777
  }
649
778
  function configureLeftPinnedSortable() {
650
779
  leftPinnedSortable.option("animation", 150);
651
- leftPinnedSortable.option("handle", '[data-slot="table-configurator-initial-state-left-drag-handle"]');
780
+ leftPinnedSortable.option("group", TABLE_CONFIGURATOR_INITIAL_STATE_SORTABLE_GROUP);
652
781
  leftPinnedSortable.option("onUpdate", handleLeftPinnedSortableUpdate);
782
+ leftPinnedSortable.option("onAdd", handleInitialStateSortableAdd);
653
783
  }
654
784
  function configureCenterSortable() {
655
785
  centerSortable.option("animation", 150);
656
- centerSortable.option("handle", '[data-slot="table-configurator-initial-state-center-drag-handle"]');
786
+ centerSortable.option("group", TABLE_CONFIGURATOR_INITIAL_STATE_SORTABLE_GROUP);
657
787
  centerSortable.option("onUpdate", handleCenterSortableUpdate);
788
+ centerSortable.option("onAdd", handleInitialStateSortableAdd);
658
789
  }
659
790
  function configureRightPinnedSortable() {
660
791
  rightPinnedSortable.option("animation", 150);
661
- rightPinnedSortable.option("handle", '[data-slot="table-configurator-initial-state-right-drag-handle"]');
792
+ rightPinnedSortable.option("group", TABLE_CONFIGURATOR_INITIAL_STATE_SORTABLE_GROUP);
662
793
  rightPinnedSortable.option("onUpdate", handleRightPinnedSortableUpdate);
794
+ rightPinnedSortable.option("onAdd", handleInitialStateSortableAdd);
795
+ }
796
+ function configurePaginationPageSizesSortable() {
797
+ paginationPageSizesSortable.option("animation", 150);
798
+ paginationPageSizesSortable.option("handle", '[data-slot="table-configurator-pagination-page-size-drag-handle"]');
799
+ paginationPageSizesSortable.option("onUpdate", handlePaginationPageSizesSortableUpdate);
663
800
  }
664
801
  function configureChildSortable() {
665
802
  childSortable.option("animation", 150);
@@ -670,6 +807,7 @@ async function refreshSortable() {
670
807
  leftPinnedSortable.stop();
671
808
  centerSortable.stop();
672
809
  rightPinnedSortable.stop();
810
+ paginationPageSizesSortable.stop();
673
811
  childSortable.stop();
674
812
  if (!open.value) {
675
813
  return;
@@ -679,9 +817,11 @@ async function refreshSortable() {
679
817
  leftPinnedSortable.start();
680
818
  centerSortable.start();
681
819
  rightPinnedSortable.start();
820
+ paginationPageSizesSortable.start();
682
821
  configureLeftPinnedSortable();
683
822
  configureCenterSortable();
684
823
  configureRightPinnedSortable();
824
+ configurePaginationPageSizesSortable();
685
825
  return;
686
826
  }
687
827
  if (selectedColumnNode.value) {
@@ -692,9 +832,18 @@ async function refreshSortable() {
692
832
  watch(rootInitialStateItems, () => {
693
833
  normalizeInitialStateGroups();
694
834
  }, { immediate: true });
695
- watch([draftColumnTree, selectedItemId, leftPinnedRootItemIds, centerRootItemIds, rightPinnedRootItemIds], () => {
835
+ watch([leftPinnedRootItemIds, centerRootItemIds, rightPinnedRootItemIds], () => {
836
+ syncDraftColumnTreeRootOrder();
837
+ }, { immediate: true });
838
+ watch([draftColumnTree, draftPaginationPageSizes, selectedItemId, leftPinnedRootItemIds, centerRootItemIds, rightPinnedRootItemIds], () => {
696
839
  syncSortableItemIds();
697
840
  }, { immediate: true });
841
+ watch(() => draftPaginationPageSizes.value.length, async () => {
842
+ if (!open.value || selectedItemId.value !== "general") {
843
+ return;
844
+ }
845
+ await refreshSortable();
846
+ });
698
847
  watch(open, async (value) => {
699
848
  if (value) {
700
849
  resetDraftState(props.config);
@@ -704,6 +853,7 @@ watch(open, async (value) => {
704
853
  leftPinnedSortable.stop();
705
854
  centerSortable.stop();
706
855
  rightPinnedSortable.stop();
856
+ paginationPageSizesSortable.stop();
707
857
  childSortable.stop();
708
858
  childSearch.value = "";
709
859
  search.value = "";
@@ -744,15 +894,14 @@ watch(filteredColumnItems, (items) => {
744
894
  watch(selectedColumnRendererId, () => {
745
895
  clearRendererValidation();
746
896
  });
747
- function hasVisibleTitle(value) {
748
- return value?.some((item) => item.message.trim().length > 0) ?? false;
749
- }
750
897
  function normalizeColumn(column) {
751
- const title = hasVisibleTitle(column.title) ? column.title : void 0;
752
- const tooltip = hasVisibleTitle(column.tooltip) ? column.tooltip : void 0;
898
+ const title = hasVisibleLocaleValue(column.title) ? column.title : void 0;
899
+ const tooltip = hasVisibleLocaleValue(column.tooltip) ? column.tooltip : void 0;
753
900
  const normalizedChildren = column.columns?.map(normalizeColumn);
901
+ const shouldDropGeneratedId = looksLikeGeneratedColumnId(column.id) && normalizedChildren === void 0 && column.accessor !== void 0;
754
902
  return {
755
903
  ...column,
904
+ id: shouldDropGeneratedId ? void 0 : column.id,
756
905
  title,
757
906
  tooltip,
758
907
  columns: normalizedChildren?.length ? normalizedChildren : void 0
@@ -777,21 +926,6 @@ function selectGeneral() {
777
926
  function selectItem(itemId) {
778
927
  selectedItemId.value = itemId;
779
928
  }
780
- function moveRootItemToGroup(itemId, group) {
781
- const nextLeftItemIds = leftPinnedRootItemIds.value.filter((currentItemId) => currentItemId !== itemId);
782
- const nextCenterItemIds = centerRootItemIds.value.filter((currentItemId) => currentItemId !== itemId);
783
- const nextRightItemIds = rightPinnedRootItemIds.value.filter((currentItemId) => currentItemId !== itemId);
784
- if (group === "left") {
785
- nextLeftItemIds.push(itemId);
786
- } else if (group === "center") {
787
- nextCenterItemIds.push(itemId);
788
- } else {
789
- nextRightItemIds.push(itemId);
790
- }
791
- leftPinnedRootItemIds.value = nextLeftItemIds;
792
- centerRootItemIds.value = nextCenterItemIds;
793
- rightPinnedRootItemIds.value = nextRightItemIds;
794
- }
795
929
  function addColumn() {
796
930
  const nextNode = createTableConfiguratorDraftColumnNode({
797
931
  id: crypto.randomUUID()
@@ -1077,9 +1211,34 @@ function updateDraftPaginationRight(value) {
1077
1211
  function updateDraftPageSize(value) {
1078
1212
  draftPageSize.value = normalizeOptionalNumber(value);
1079
1213
  }
1214
+ function updateDraftPaginationPageSizeOption(itemId, value) {
1215
+ const normalizedValue = normalizeOptionalNumber(value);
1216
+ draftPaginationPageSizes.value = draftPaginationPageSizes.value.map((item) => item.itemId === itemId ? {
1217
+ ...item,
1218
+ value: normalizedValue
1219
+ } : item);
1220
+ }
1221
+ function addDraftPaginationPageSizeOption() {
1222
+ draftPaginationPageSizes.value = [
1223
+ ...draftPaginationPageSizes.value,
1224
+ createPaginationPageSizeItem()
1225
+ ];
1226
+ }
1227
+ function removeDraftPaginationPageSizeOption(itemId) {
1228
+ draftPaginationPageSizes.value = draftPaginationPageSizes.value.filter((item) => item.itemId !== itemId);
1229
+ }
1230
+ function buildNextPaginationPageSizes() {
1231
+ return normalizePaginationPageSizes(draftPaginationPageSizes.value.flatMap((item) => {
1232
+ if (item.value === void 0) {
1233
+ return [];
1234
+ }
1235
+ return [item.value];
1236
+ }));
1237
+ }
1080
1238
  function buildNextPaginationState() {
1081
1239
  const currentPagination = draftBaseConfig.value.props?.initialState?.pagination;
1082
- const nextPageSize = draftPageSize.value;
1240
+ const nextPaginationPageSizes = buildNextPaginationPageSizes();
1241
+ const nextPageSize = resolvePaginationPageSize(draftPageSize.value, nextPaginationPageSizes);
1083
1242
  if (!currentPagination && nextPageSize === void 0) {
1084
1243
  return void 0;
1085
1244
  }
@@ -1098,6 +1257,7 @@ function buildDraftConfig() {
1098
1257
  cellStyles: draftCellStyles.value,
1099
1258
  paginationLeft: normalizeOptionalText(draftPaginationLeft.value),
1100
1259
  paginationRight: normalizeOptionalText(draftPaginationRight.value),
1260
+ paginationPageSizes: buildNextPaginationPageSizes(),
1101
1261
  columns: materializeTableConfiguratorColumnTree(draftColumnTree.value).map(normalizeColumn),
1102
1262
  props: {
1103
1263
  ...draftBaseConfig.value.props,
@@ -1114,68 +1274,320 @@ function buildDraftConfig() {
1114
1274
  }
1115
1275
  });
1116
1276
  }
1117
- function triggerImport() {
1118
- importInputRef.value?.click();
1119
- }
1120
1277
  function showImportError(message) {
1121
1278
  $toast.error(message);
1122
1279
  }
1123
- function showExportError() {
1124
- $toast.error(t("export-config-failed"));
1280
+ function showCopyError(message) {
1281
+ $toast.error(message);
1125
1282
  }
1126
- async function handleImportChange(event) {
1127
- const target = event.target;
1128
- if (!(target instanceof HTMLInputElement)) {
1129
- return;
1283
+ function showImportErrorWithCopyAction(message, onClick) {
1284
+ $toast.error(message, {
1285
+ action: {
1286
+ label: t("copy-paste-error"),
1287
+ onClick
1288
+ }
1289
+ });
1290
+ }
1291
+ function getValidDraftConfig(errorMessage) {
1292
+ if (!validateExpressionEditors()) {
1293
+ showCopyError(errorMessage);
1294
+ return void 0;
1130
1295
  }
1131
- const file = target.files?.[0];
1132
- try {
1133
- if (!file) {
1134
- return;
1296
+ const result = buildDraftConfig();
1297
+ if (!result.success) {
1298
+ showCopyError(errorMessage);
1299
+ return void 0;
1300
+ }
1301
+ return result.data;
1302
+ }
1303
+ function buildTableConfigJsonSchema() {
1304
+ return z.toJSONSchema(TableConfigC, {
1305
+ io: "input",
1306
+ unrepresentable: "any"
1307
+ });
1308
+ }
1309
+ function collectUsedRendererIds(columns) {
1310
+ const rendererIds = [];
1311
+ for (const column of columns) {
1312
+ if (Array.isArray(column.columns) && column.columns.length > 0) {
1313
+ for (const rendererId2 of collectUsedRendererIds(column.columns)) {
1314
+ if (!rendererIds.includes(rendererId2)) {
1315
+ rendererIds.push(rendererId2);
1316
+ }
1317
+ }
1318
+ continue;
1135
1319
  }
1136
- const source = await file.text();
1137
- let parsedValue;
1138
- try {
1139
- parsedValue = JSON.parse(source);
1140
- } catch {
1141
- showImportError(t("import-config-invalid-json"));
1142
- return;
1320
+ const rendererId = getColumnRendererId(column);
1321
+ if (!rendererIds.includes(rendererId)) {
1322
+ rendererIds.push(rendererId);
1143
1323
  }
1144
- const result = TableConfigC.safeParse(parsedValue);
1145
- if (!result.success) {
1146
- showImportError(t("import-config-invalid-schema"));
1147
- return;
1324
+ }
1325
+ return rendererIds;
1326
+ }
1327
+ function buildRendererSchemaMarkdown(config) {
1328
+ const rendererIds = collectUsedRendererIds(config.columns);
1329
+ if (rendererIds.length === 0) {
1330
+ return "\u5F53\u524D\u8349\u7A3F\u4E2D\u6CA1\u6709\u53F6\u5B50\u5217\u6E32\u67D3\u5668\u3002";
1331
+ }
1332
+ return rendererIds.map((rendererId) => {
1333
+ const renderer = getTableRenderer(rendererId);
1334
+ if (!renderer) {
1335
+ return [
1336
+ `### ${rendererId}`,
1337
+ "\u672A\u627E\u5230\u8BE5\u6E32\u67D3\u5668\u7684 options schema\u3002"
1338
+ ].join("\n");
1148
1339
  }
1149
- resetDraftState(result.data);
1150
- await nextTick();
1151
- await refreshSortable();
1152
- } finally {
1153
- target.value = "";
1340
+ return [
1341
+ `### ${rendererId}`,
1342
+ "```json",
1343
+ JSON.stringify(z.toJSONSchema(renderer.optionsSchema, {
1344
+ io: "input",
1345
+ unrepresentable: "any"
1346
+ }), null, 2),
1347
+ "```"
1348
+ ].join("\n");
1349
+ }).join("\n\n");
1350
+ }
1351
+ function buildDslGuideMarkdown() {
1352
+ return [
1353
+ "## DSL / CEL \u7F16\u5199\u8BF4\u660E",
1354
+ "\u672C\u914D\u7F6E\u4E2D\u7684 DSL \u662F CEL \u8868\u8FBE\u5F0F\uFF1B\u8868\u8FBE\u5F0F\u5B57\u6BB5\u5FC5\u987B\u586B\u5199\u5B57\u7B26\u4E32\uFF0C\u4E0D\u8981\u751F\u6210 JavaScript\u3001TypeScript\u3001\u7BAD\u5934\u51FD\u6570\u6216\u4F2A\u4EE3\u7801\u3002",
1355
+ "",
1356
+ "### 1. \u57FA\u7840\u8BED\u6CD5",
1357
+ '- \u5B57\u9762\u91CF\uFF1A`"text"`\u3001`123`\u3001`123.45`\u3001`true`\u3001`false`\u3001`null`\u3002',
1358
+ '- \u5217\u8868 / \u5BF9\u8C61\uFF1A`[1, 2, 3]`\u3001`{"a": 1, "b": 2}`\u3002',
1359
+ '- \u8BBF\u95EE\uFF1A`.`\u3001`[]`\uFF0C\u4F8B\u5982 `row.id`\u3001`row["id"]`\u3001`row.items[0]`\u3002',
1360
+ '- \u53EF\u9009\u8BBF\u95EE\uFF1A`.?`\u3001`[?]`\uFF0C\u4F8B\u5982 `row.?children`\u3001`row[?"name"]`\u3002',
1361
+ "- \u903B\u8F91\u4E0E\u6761\u4EF6\uFF1A`&&`\u3001`||`\u3001`!`\u3001`condition ? a : b`\u3002",
1362
+ "- \u6BD4\u8F83\uFF1A`==`\u3001`!=`\u3001`<`\u3001`<=`\u3001`>`\u3001`>=`\u3001`in`\u3002",
1363
+ "- \u7B97\u672F\uFF1A`+`\u3001`-`\u3001`*`\u3001`/`\u3001`%`\u3002",
1364
+ '- \u65B9\u6CD5\u8C03\u7528\uFF1A`value.method(args)`\uFF0C\u4F8B\u5982 `now.format("yyyy-MM-dd")`\u3001`row.?children.orValue([])`\u3002',
1365
+ "- `accessor` \u4E3A\u5B57\u7B26\u4E32\u65F6\u4E0D\u662F CEL\uFF0C\u800C\u662F dot-prop \u8DEF\u5F84\uFF0C\u4F8B\u5982 `id`\u3001`foo.bar`\u3001`foo[0].bar`\u3002",
1366
+ "",
1367
+ "### 2. \u5168\u5C40\u53D8\u91CF\u4E0E\u5E38\u91CF",
1368
+ "- `now: Date` \u5F53\u524D\u65F6\u95F4\u3002",
1369
+ "- `today: Date` \u4E0E `now` \u7C7B\u4F3C\uFF0C\u4FDD\u7559\u7528\u4E8E\u517C\u5BB9\u65E7\u914D\u7F6E\u3002",
1370
+ "- `location: URL` \u5F53\u524D\u9875\u9762 URL\u3002",
1371
+ "- `token: string` \u5F53\u524D sessionStorage \u4E2D\u7684 token\uFF0C\u7F3A\u5931\u65F6\u4E3A\u7A7A\u5B57\u7B26\u4E32\u3002",
1372
+ "- `locale: string` \u5F53\u524D\u6D4F\u89C8\u5668\u8BED\u8A00\u3002",
1373
+ "- `git: map<string, string>` \u6765\u81EA\u8FD0\u884C\u65F6\u914D\u7F6E\u7684 git \u5E38\u91CF\u3002",
1374
+ "- `ci: map<string, dyn>` \u6765\u81EA\u8FD0\u884C\u65F6\u914D\u7F6E\u7684 CI \u5E38\u91CF\uFF0C\u5176\u4E2D `ci.build` \u4F1A\u88AB\u8F6C\u6210 `int`\u3002",
1375
+ "- \u8FD8\u4F1A\u6DF7\u5165\u5BBF\u4E3B\u5E94\u7528\u901A\u8FC7 `props.dsl` \u6CE8\u5165\u7684\u5168\u5C40\u52A8\u6001\u4E0A\u4E0B\u6587\uFF1B\u8FD9\u4E9B\u952E\u5B58\u5728\u65F6\u53EF\u76F4\u63A5\u8BBF\u95EE\uFF0C\u4F46\u540D\u79F0\u548C\u7C7B\u578B\u4E0D\u56FA\u5B9A\uFF0C\u5E94\u4EE5\u5F53\u524D\u914D\u7F6E\u548C\u4E1A\u52A1\u4E0A\u4E0B\u6587\u4E3A\u51C6\u3002",
1376
+ "",
1377
+ "### 3. \u8868\u683C\u914D\u7F6E\u5B57\u6BB5\u91CC\u7684\u5C40\u90E8\u53D8\u91CF",
1378
+ "- `accessor.read`: `row: dyn`\u3001`index: int`\u3002",
1379
+ "- `getRowId`: `row: dyn`\u3001`index: int`\u3001`parent: dyn`\uFF1B\u5FC5\u987B\u8FD4\u56DE `string`\u3002",
1380
+ "- `getSubRows`: `row: dyn`\u3001`index: int`\uFF1B\u901A\u5E38\u8FD4\u56DE `list<dyn>`\u3002",
1381
+ "- `enableRowSelection`: `row: dyn`\u3001`index: int`\u3001`id: string`\uFF1B\u5FC5\u987B\u8FD4\u56DE `bool`\u3002",
1382
+ "- `enableMultiRowSelection`: `row: dyn`\u3001`index: int`\u3001`id: string`\uFF1B\u5FC5\u987B\u8FD4\u56DE `bool`\u3002",
1383
+ "- `cellStyles`: `row: dyn`\u3001`index: int`\u3001`id: string`\u3001`selected: bool`\u3001`pinned: dyn`\uFF1B\u5FC5\u987B\u8FD4\u56DE `map`\u3002",
1384
+ "- `table.renderer.text.props.copyExpression`: `row: dyn`\u3001`index: int`\u3002",
1385
+ "",
1386
+ "### 4. \u53EF\u7528\u7C7B\u578B\u4E0E\u6269\u5C55\u80FD\u529B",
1387
+ "- `URL` \u7C7B\u578B\uFF1A\u53EF\u8C03\u7528 `location.searchParams()`\uFF0C\u8FD4\u56DE `URLSearchParams`\u3002",
1388
+ "- `URLSearchParams` \u7C7B\u578B\uFF1A\u53EF\u8C03\u7528 `searchParams({...})` \u521B\u5EFA\uFF0C`URLSearchParams.get(string)` \u8FD4\u56DE `optional<string>`\u3002",
1389
+ "- `Date` \u7C7B\u578B\uFF1A\u7531 `date(string)` \u521B\u5EFA\uFF0C\u4E5F\u53EF\u76F4\u63A5\u4F7F\u7528 `now` / `today`\u3002",
1390
+ "- `Date` \u652F\u6301\u6BD4\u8F83\uFF1A`<`\u3001`<=`\u3001`>`\u3001`>=`\u3001`==`\u3002",
1391
+ "- `Date.startOf(string)` / `Date.endOf(string)`\uFF1A`unit` \u652F\u6301 `day` / `week` / `month` / `year`\u3002",
1392
+ "- `Date.offset(int, string)`\uFF1A`unit` \u652F\u6301 `day|days|week|weeks|month|months|year|years`\u3002",
1393
+ "- `Date.set(string, int)`\uFF1A`unit` \u652F\u6301 `day` / `month` / `year`\uFF0C\u5176\u4E2D `month` \u4F20\u5165\u81EA\u7136\u6708 1-12\u3002",
1394
+ '- `Date.format(string)`\uFF1A\u4F7F\u7528 `date-fns` \u683C\u5F0F\u5316\u5B57\u7B26\u4E32\uFF0C\u4F8B\u5982 `now.format("yyyy-MM-dd")`\u3002',
1395
+ "- `list<double>.sum()` / `list<int>.sum()`\uFF1A\u5BF9\u6570\u5B57\u5217\u8868\u6C42\u548C\uFF0C\u8FD4\u56DE `double`\u3002",
1396
+ "- `double.toLocaleString(dyn)` / `int.toLocaleString(dyn)`\uFF1A\u6309\u5F53\u524D\u8BED\u8A00\u683C\u5F0F\u5316\u6570\u5B57\u3002",
1397
+ "- `double.encodeSimplifiedChineseUppercase()` / `int.encodeSimplifiedChineseUppercase()`\uFF1A\u8F6C\u4E2D\u6587\u5927\u5199\u91D1\u989D/\u6570\u5B57\u3002",
1398
+ "- `parseJSON(string): dyn`\uFF1A\u628A JSON \u5B57\u7B26\u4E32\u89E3\u6790\u6210\u5BF9\u8C61\u6216\u5217\u8868\u3002",
1399
+ "- `string.slice(int)` / `string.slice(int, int)`\uFF1A\u5B57\u7B26\u4E32\u5207\u7247\u3002",
1400
+ "- `session(string): optional<string>`\uFF1A\u4ECE `sessionStorage` \u8BFB\u53D6\u503C\u3002",
1401
+ "- `local(string): optional<string>`\uFF1A\u4ECE `localStorage` \u8BFB\u53D6\u503C\u3002",
1402
+ "",
1403
+ "### 5. Optional / \u7A7A\u503C\u8BED\u4E49",
1404
+ "- \u53EF\u9009\u8BBF\u95EE .? \u548C [?] \u4F1A\u8FD4\u56DE `Optional`\uFF0C\u800C\u4E0D\u662F\u76F4\u63A5\u8FD4\u56DE\u666E\u901A\u503C\u3002",
1405
+ "- `Optional.hasValue()` \u5224\u65AD\u662F\u5426\u5B58\u5728\u503C\u3002",
1406
+ "- `Optional.value()` \u53D6\u503C\uFF1B\u5728\u7A7A\u503C\u65F6\u4F1A\u629B\u9519\uFF0C\u4E00\u822C\u4E0D\u5EFA\u8BAE\u76F4\u63A5\u4F7F\u7528\u3002",
1407
+ "- `Optional.or(optional)` \u5728\u7A7A\u503C\u65F6\u4F7F\u7528\u53E6\u4E00\u4E2A Optional\u3002",
1408
+ "- `Optional.orValue(defaultValue)` \u5728\u7A7A\u503C\u65F6\u8FD4\u56DE\u9ED8\u8BA4\u503C\uFF0C\u6700\u5E38\u7528\u3002\u793A\u4F8B\uFF1A`row.?children.orValue([])`\u3002",
1409
+ "",
1410
+ "### 6. \u7F16\u5199\u8981\u6C42",
1411
+ "- \u751F\u6210\u8868\u8FBE\u5F0F\u65F6\u8981\u4E25\u683C\u9075\u5B88\u5B57\u6BB5\u8FD4\u56DE\u7C7B\u578B\uFF0C\u4E0D\u8981\u6DF7\u7528\u5B57\u7B26\u4E32\u3001\u5E03\u5C14\u503C\u3001\u6570\u7EC4\u548C\u5BF9\u8C61\u3002",
1412
+ "- \u5982\u679C\u5B57\u6BB5 schema \u6216\u5C40\u90E8\u53D8\u91CF\u6CA1\u6709\u58F0\u660E\u67D0\u4E2A\u53D8\u91CF\uFF0C\u5C31\u4E0D\u8981\u81C6\u9020\u65B0\u7684\u53D8\u91CF\u540D\u3002",
1413
+ "- \u4F18\u5148\u590D\u7528\u5F53\u524D\u914D\u7F6E\u4E2D\u5DF2\u7ECF\u51FA\u73B0\u8FC7\u7684\u5B57\u6BB5\u8DEF\u5F84\u3001\u8868\u8FBE\u5F0F\u6A21\u5F0F\u548C\u8FD4\u56DE\u7ED3\u6784\u3002"
1414
+ ].join("\n");
1415
+ }
1416
+ function buildMarkdownNotes() {
1417
+ return [
1418
+ "## \u6CE8\u610F\u4E8B\u9879",
1419
+ "- \u8BF7\u4F18\u5148\u4F9D\u636E schema \u4E2D\u7684 `description` \u7F16\u5199\u914D\u7F6E\uFF0C\u63CF\u8FF0\u8D8A\u8BE6\u7EC6\u8D8A\u91CD\u8981\uFF0C\u4E0D\u8981\u5FFD\u7565\u5B57\u6BB5\u8BF4\u660E\u3002",
1420
+ "- \u53EA\u5141\u8BB8\u4FEE\u6539\u73B0\u6709\u5217\u7684\u914D\u7F6E\uFF1B\u7981\u6B62\u65B0\u589E\u9876\u7EA7\u5217\u3001\u7981\u6B62\u65B0\u589E\u5B50\u5217\u3001\u7981\u6B62\u5220\u9664\u540E\u91CD\u5EFA\u5217\u3002",
1421
+ "- \u7981\u6B62\u751F\u6210\u65B0\u7684\u5217 ID\uFF0C\u4E5F\u4E0D\u8981\u4E3A\u7F3A\u5931 ID \u7684\u65B0\u5217\u8865 ID\uFF1B\u5982\u679C\u7528\u6237\u9700\u8981\u65B0\u589E\u5217\uFF0C\u5FC5\u987B\u5728\u754C\u9762\u4E2D\u624B\u52A8\u5B8C\u6210\u3002",
1422
+ "- \u4FEE\u6539\u5217\u914D\u7F6E\u65F6\u5C3D\u91CF\u4FDD\u7559\u73B0\u6709\u5217\u987A\u5E8F\u3001\u5C42\u7EA7\u7ED3\u6784\u3001`id`\u3001`accessor` \u548C renderer \u8BED\u4E49\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u53D8\u66F4\u3002",
1423
+ "- \u5982\u679C\u9700\u6C42\u8D85\u51FA schema \u80FD\u8868\u8FBE\u7684\u8303\u56F4\uFF0C\u5E94\u8BE5\u76F4\u63A5\u8BF4\u660E\u9650\u5236\uFF0C\u800C\u4E0D\u662F\u53D1\u660E schema \u4E2D\u4E0D\u5B58\u5728\u7684\u65B0\u5B57\u6BB5\u3002"
1424
+ ].join("\n");
1425
+ }
1426
+ function buildAiPromptHeaderMarkdown() {
1427
+ return [
1428
+ "# \u8868\u683C\u914D\u7F6E AI \u4E0A\u4E0B\u6587",
1429
+ "\u4F60\u662F\u4E00\u4E2A\u5E2E\u52A9\u7528\u6237\u914D\u7F6E\u8868\u683C\u7EC4\u4EF6\u7684 AI \u52A9\u624B\uFF0C\u8D1F\u8D23\u6839\u636E\u5F53\u524D\u914D\u7F6E\u3001schema \u548C DSL \u89C4\u5219\u56DE\u7B54\u7528\u6237\u95EE\u9898\u3002",
1430
+ "\u4F60\u7684\u76EE\u6807\u662F\u89E3\u91CA\u73B0\u6709\u914D\u7F6E\u3001\u5E2E\u52A9\u7528\u6237\u4FEE\u6539\u73B0\u6709\u914D\u7F6E\uFF0C\u5E76\u5728\u7EA6\u675F\u8303\u56F4\u5185\u751F\u6210\u53EF\u7528\u7684\u914D\u7F6E\u7247\u6BB5\u3002",
1431
+ "\u5982\u679C\u4F60\u4E0D\u786E\u5B9A\u67D0\u4E2A\u5B57\u6BB5\u3001\u53D8\u91CF\u3001\u7C7B\u578B\u3001\u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587\u6216\u4E1A\u52A1\u542B\u4E49\uFF0C\u5FC5\u987B\u660E\u786E\u8BF4\u660E\u4E0D\u786E\u5B9A\u70B9\uFF0C\u5E76\u8981\u6C42\u7528\u6237\u8865\u5145\u4FE1\u606F\uFF1B\u4E0D\u8981\u731C\u6D4B\uFF0C\u4E0D\u8981\u53D1\u660E schema \u4E2D\u4E0D\u5B58\u5728\u7684\u5B57\u6BB5\u3001\u53D8\u91CF\u3001\u51FD\u6570\u6216\u80FD\u529B\u3002",
1432
+ "\u53EA\u6709\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u201C\u751F\u6210\u914D\u7F6E\u201D\u201C\u8F93\u51FA\u914D\u7F6E\u201D\u201C\u76F4\u63A5\u7ED9\u6211\u914D\u7F6E\u201D\u201C\u8FD4\u56DE\u53EF\u7C98\u8D34\u914D\u7F6E\u201D\u8FD9\u7C7B\u7ED3\u679C\u65F6\uFF0C\u624D\u8FD4\u56DE\u5B8C\u6574\u914D\u7F6E\u6216\u914D\u7F6E\u7247\u6BB5\uFF1B\u5176\u4ED6\u60C5\u51B5\u4E0B\u4F18\u5148\u89E3\u91CA\u601D\u8DEF\u3001\u6307\u51FA\u4FEE\u6539\u70B9\u548C\u539F\u56E0\u3002",
1433
+ "\u5F53\u6211\u8BF4\u201C\u751F\u6210\u914D\u7F6E\u201D\u65F6\uFF0C\u6211\u4F1A\u628A\u914D\u7F6E\u4EE5 Markdown code block \u7684\u5F62\u5F0F\u53D1\u7ED9\u4F60\uFF1B\u4F60\u8FD4\u56DE\u7684\u914D\u7F6E\u4E5F\u5E94\u8BE5\u653E\u5728 Markdown code block \u4E2D\uFF0C\u8FD9\u6837\u6211\u53EF\u4EE5\u76F4\u63A5\u7C98\u8D34\u5230\u9875\u9762\u914D\u7F6E\u91CC\u3002"
1434
+ ].join("\n");
1435
+ }
1436
+ function buildMarkdownCopyContent(config) {
1437
+ return [
1438
+ buildAiPromptHeaderMarkdown(),
1439
+ "",
1440
+ "## \u5F53\u524D\u914D\u7F6E",
1441
+ "```json",
1442
+ JSON.stringify(config, null, 2),
1443
+ "```",
1444
+ "",
1445
+ buildDslGuideMarkdown(),
1446
+ "",
1447
+ buildMarkdownNotes(),
1448
+ "",
1449
+ "## TableConfig JSON Schema",
1450
+ "```json",
1451
+ JSON.stringify(buildTableConfigJsonSchema(), null, 2),
1452
+ "```",
1453
+ "",
1454
+ "## \u5F53\u524D\u4F7F\u7528\u7684 Renderer Options Schema",
1455
+ buildRendererSchemaMarkdown(config)
1456
+ ].join("\n");
1457
+ }
1458
+ function formatIssuePath(path) {
1459
+ if (path.length === 0) {
1460
+ return "(root)";
1461
+ }
1462
+ return path.map((segment) => {
1463
+ if (typeof segment === "number") {
1464
+ return `${segment}`;
1465
+ }
1466
+ if (typeof segment === "string") {
1467
+ return segment;
1468
+ }
1469
+ return String(segment);
1470
+ }).join(".");
1471
+ }
1472
+ function formatIssueExtraFields(issue) {
1473
+ const lines = [];
1474
+ const entries = Object.entries(issue);
1475
+ for (const [key, value] of entries) {
1476
+ if (key === "code" || key === "message" || key === "path") {
1477
+ continue;
1478
+ }
1479
+ lines.push(` - ${key}: ${JSON.stringify(value)}`);
1480
+ }
1481
+ return lines;
1482
+ }
1483
+ function buildPasteConfigErrorDetails(source, error) {
1484
+ if (error instanceof SyntaxError) {
1485
+ return [
1486
+ "## \u7C98\u8D34\u5931\u8D25\u539F\u56E0",
1487
+ "- \u7C7B\u578B\uFF1AJSON \u89E3\u6790\u5931\u8D25",
1488
+ `- message: ${error.message}`,
1489
+ "",
1490
+ "## \u539F\u59CB\u7C98\u8D34\u5185\u5BB9",
1491
+ "```text",
1492
+ source,
1493
+ "```"
1494
+ ].join("\n");
1495
+ }
1496
+ const issueLines = error.issues.flatMap((issue, index) => {
1497
+ return [
1498
+ `### Issue ${index + 1}`,
1499
+ `- path: ${formatIssuePath(issue.path)}`,
1500
+ `- code: ${issue.code}`,
1501
+ `- message: ${issue.message}`,
1502
+ ...formatIssueExtraFields(issue)
1503
+ ];
1504
+ });
1505
+ return [
1506
+ "## \u7C98\u8D34\u5931\u8D25\u539F\u56E0",
1507
+ "- \u7C7B\u578B\uFF1ASchema \u6821\u9A8C\u5931\u8D25",
1508
+ "",
1509
+ "## \u539F\u59CB\u7C98\u8D34\u5185\u5BB9",
1510
+ "```json",
1511
+ source,
1512
+ "```",
1513
+ "",
1514
+ "## Schema \u62A5\u9519",
1515
+ ...issueLines
1516
+ ].join("\n");
1517
+ }
1518
+ function buildPasteConfigErrorMarkdown(source, error) {
1519
+ return [
1520
+ buildAiPromptHeaderMarkdown(),
1521
+ "",
1522
+ "## \u5F53\u524D\u4EFB\u52A1",
1523
+ "\u7528\u6237\u628A\u4E00\u6BB5\u914D\u7F6E\u7C98\u8D34\u56DE\u8868\u683C\u914D\u7F6E\u5668\u65F6\u5931\u8D25\u4E86\u3002\u8BF7\u57FA\u4E8E\u4E0B\u9762\u7684\u539F\u59CB\u5185\u5BB9\u548C\u62A5\u9519\u4FEE\u590D\u5F53\u524D\u914D\u7F6E\u3002",
1524
+ "\u4F60\u53EA\u80FD\u4FEE\u590D\u5F53\u524D\u914D\u7F6E\uFF0C\u4E0D\u80FD\u65B0\u589E\u5217\u3001\u4E0D\u80FD\u65B0\u589E\u5B50\u5217\u3001\u4E0D\u80FD\u5220\u9664\u540E\u91CD\u5EFA\u5217\u3001\u4E0D\u80FD\u751F\u6210\u65B0\u7684\u5217 ID\u3002",
1525
+ "\u5982\u679C\u67D0\u4E2A\u5217\u7F3A\u5C11 ID\uFF0C\u4E0D\u8981\u66FF\u7528\u6237\u8865\u4E00\u4E2A\u65B0 ID\uFF1B\u8FD9\u7C7B\u64CD\u4F5C\u5FC5\u987B\u5728\u754C\u9762\u4E2D\u5B8C\u6210\u3002",
1526
+ "\u8BF7\u4F18\u5148\u4FEE\u590D\u6700\u5C0F\u5FC5\u8981\u8303\u56F4\uFF0C\u53EA\u4FEE\u6539\u5BFC\u81F4\u62A5\u9519\u7684\u90E8\u5206\u3002",
1527
+ "\u53EA\u6709\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u8F93\u51FA\u914D\u7F6E\u65F6\uFF0C\u624D\u8FD4\u56DE\u5B8C\u6574\u914D\u7F6E\uFF1B\u5982\u679C\u8FD4\u56DE\u914D\u7F6E\uFF0C\u5FC5\u987B\u653E\u5728 Markdown code block \u4E2D\u3002",
1528
+ "",
1529
+ buildPasteConfigErrorDetails(source, error),
1530
+ "",
1531
+ buildMarkdownNotes()
1532
+ ].join("\n");
1533
+ }
1534
+ async function writeClipboardText(value, errorMessage) {
1535
+ try {
1536
+ await navigator.clipboard.writeText(value);
1537
+ } catch {
1538
+ showCopyError(errorMessage);
1154
1539
  }
1155
1540
  }
1156
- function exportConfig() {
1157
- if (!validateExpressionEditors()) {
1158
- showExportError();
1541
+ async function copyPasteConfigError(source, error) {
1542
+ await writeClipboardText(buildPasteConfigErrorMarkdown(source, error), t("copy-paste-error-failed"));
1543
+ }
1544
+ async function pasteConfigFromClipboard() {
1545
+ let source = "";
1546
+ try {
1547
+ source = await navigator.clipboard.readText();
1548
+ } catch {
1549
+ showImportError(t("paste-config-read-failed"));
1159
1550
  return;
1160
1551
  }
1161
- const result = buildDraftConfig();
1552
+ let parsedValue;
1553
+ try {
1554
+ parsedValue = JSON.parse(source);
1555
+ } catch (error) {
1556
+ if (error instanceof SyntaxError) {
1557
+ showImportErrorWithCopyAction(
1558
+ t("paste-config-invalid-json"),
1559
+ async () => copyPasteConfigError(source, error)
1560
+ );
1561
+ return;
1562
+ }
1563
+ showImportError(t("paste-config-invalid-json"));
1564
+ return;
1565
+ }
1566
+ const result = TableConfigC.safeParse(parsedValue);
1162
1567
  if (!result.success) {
1163
- showExportError();
1568
+ showImportErrorWithCopyAction(
1569
+ t("paste-config-invalid-schema"),
1570
+ async () => copyPasteConfigError(source, result.error)
1571
+ );
1164
1572
  return;
1165
1573
  }
1166
- try {
1167
- const blob = new Blob([JSON.stringify(result.data, null, 2)], { type: "application/json" });
1168
- const url = URL.createObjectURL(blob);
1169
- const link = document.createElement("a");
1170
- link.href = url;
1171
- link.download = "table-config.json";
1172
- document.body.append(link);
1173
- link.click();
1174
- link.remove();
1175
- URL.revokeObjectURL(url);
1176
- } catch {
1177
- showExportError();
1574
+ resetDraftState(result.data);
1575
+ await nextTick();
1576
+ await refreshSortable();
1577
+ }
1578
+ async function copyConfig() {
1579
+ const config = getValidDraftConfig(t("copy-config-failed"));
1580
+ if (!config) {
1581
+ return;
1582
+ }
1583
+ await writeClipboardText(JSON.stringify(config, null, 2), t("copy-config-failed"));
1584
+ }
1585
+ async function copyMarkdown() {
1586
+ const config = getValidDraftConfig(t("copy-markdown-failed"));
1587
+ if (!config) {
1588
+ return;
1178
1589
  }
1590
+ await writeClipboardText(buildMarkdownCopyContent(config), t("copy-markdown-failed"));
1179
1591
  }
1180
1592
  function confirmChanges() {
1181
1593
  if (!validateExpressionEditors()) {
@@ -1199,6 +1611,7 @@ function confirmChanges() {
1199
1611
  <DialogContent
1200
1612
  class="flex h-[min(42rem,calc(100vh-4rem))] w-[calc(100%-2rem)] max-h-[calc(100vh-4rem)] max-w-[calc(100%-2rem)] flex-col overflow-hidden p-0 sm:w-[80vw] sm:max-w-[80vw]"
1201
1613
  :show-close-button="true"
1614
+ @pointer-down-outside="(event) => event.preventDefault()"
1202
1615
  >
1203
1616
  <DialogHeader class="gap-1 border-b border-zinc-200 px-6 py-5">
1204
1617
  <div class="flex items-center gap-3">
@@ -1209,21 +1622,13 @@ function confirmChanges() {
1209
1622
  type="button"
1210
1623
  variant="ghost"
1211
1624
  size="sm"
1212
- data-slot="table-configurator-import"
1625
+ data-slot="table-configurator-paste"
1213
1626
  class="shrink-0"
1214
- @click="triggerImport"
1627
+ @click="pasteConfigFromClipboard"
1215
1628
  >
1216
- <Icon icon="fluent:arrow-upload-20-regular" />
1217
- {{ t("import-config") }}
1629
+ <Icon icon="fluent:clipboard-paste-20-regular" />
1630
+ {{ t("paste-config") }}
1218
1631
  </Button>
1219
- <input
1220
- ref="importInputRef"
1221
- data-slot="table-configurator-import-input"
1222
- class="hidden"
1223
- type="file"
1224
- accept="application/json,.json"
1225
- @change="handleImportChange"
1226
- >
1227
1632
  </div>
1228
1633
  <DialogDescription class="text-sm text-zinc-500">
1229
1634
  {{ t("configure-table-description") }}
@@ -1397,13 +1802,13 @@ function confirmChanges() {
1397
1802
 
1398
1803
  <section
1399
1804
  data-slot="table-configurator-pagination"
1400
- class="rounded-lg border border-zinc-200 p-4"
1805
+ class="border-t border-zinc-200 pt-4"
1401
1806
  >
1402
1807
  <h4 class="text-sm font-semibold text-zinc-800">
1403
1808
  {{ t("pagination") }}
1404
1809
  </h4>
1405
1810
 
1406
- <div class="mt-3 flex flex-col gap-4">
1811
+ <div class="mt-4 flex flex-col gap-4">
1407
1812
  <label
1408
1813
  data-field-key="paginationLeft"
1409
1814
  class="flex flex-col gap-1"
@@ -1443,186 +1848,214 @@ function confirmChanges() {
1443
1848
  <NumberFieldInput class="w-full text-left" />
1444
1849
  </NumberField>
1445
1850
  </label>
1851
+
1852
+ <div
1853
+ data-field-key="paginationPageSizes"
1854
+ class="flex flex-col gap-3"
1855
+ >
1856
+ <div class="flex items-center justify-between gap-3">
1857
+ <span class="text-xs font-medium text-zinc-500">{{ t("pagination-page-size-options") }}</span>
1858
+ <Button
1859
+ type="button"
1860
+ variant="ghost"
1861
+ size="sm"
1862
+ data-slot="table-configurator-pagination-page-size-add"
1863
+ class="h-7 px-2 text-xs"
1864
+ @click="addDraftPaginationPageSizeOption"
1865
+ >
1866
+ <Icon icon="fluent:add-20-regular" />
1867
+ {{ t("add-pagination-page-size") }}
1868
+ </Button>
1869
+ </div>
1870
+
1871
+ <div
1872
+ v-if="draftPaginationPageSizes.length > 0"
1873
+ ref="paginationPageSizesSortableListRef"
1874
+ data-slot="table-configurator-pagination-page-size-list"
1875
+ class="flex flex-col gap-2"
1876
+ >
1877
+ <InputGroup
1878
+ v-for="item in draftPaginationPageSizes"
1879
+ :key="item.itemId"
1880
+ :data-item-id="item.itemId"
1881
+ data-slot="table-configurator-pagination-page-size-item"
1882
+ class="overflow-hidden"
1883
+ >
1884
+ <InputGroupNumberField
1885
+ class="flex-1"
1886
+ :model-value="item.value"
1887
+ :min="1"
1888
+ @update:model-value="(value) => updateDraftPaginationPageSizeOption(item.itemId, value)"
1889
+ />
1890
+
1891
+ <InputGroupAddon class="w-12 shrink-0 self-stretch border-r border-zinc-200 px-0 py-0">
1892
+ <div
1893
+ data-slot="table-configurator-pagination-page-size-drag-handle"
1894
+ :title="t('drag-pagination-page-size')"
1895
+ class="flex h-full w-full cursor-grab items-center justify-center text-zinc-400 transition-colors hover:bg-zinc-50 hover:text-zinc-700 active:cursor-grabbing"
1896
+ >
1897
+ <Icon icon="fluent:re-order-dots-vertical-20-regular" />
1898
+ </div>
1899
+ </InputGroupAddon>
1900
+
1901
+ <InputGroupAddon
1902
+ align="inline-end"
1903
+ class="w-12 shrink-0 self-stretch border-l border-zinc-200 px-0 py-0 mr-0"
1904
+ >
1905
+ <button
1906
+ type="button"
1907
+ data-slot="table-configurator-pagination-page-size-remove"
1908
+ class="flex h-full w-full items-center justify-center bg-transparent text-red-600 transition-colors hover:bg-red-50 hover:text-red-700"
1909
+ :aria-label="t('remove-pagination-page-size')"
1910
+ @click="removeDraftPaginationPageSizeOption(item.itemId)"
1911
+ >
1912
+ <Icon icon="fluent:delete-20-regular" />
1913
+ </button>
1914
+ </InputGroupAddon>
1915
+ </InputGroup>
1916
+ </div>
1917
+
1918
+ <p
1919
+ v-else
1920
+ data-slot="table-configurator-pagination-page-size-empty"
1921
+ class="text-xs text-zinc-400"
1922
+ >
1923
+ {{ t("no-pagination-page-sizes") }}
1924
+ </p>
1925
+ </div>
1446
1926
  </div>
1447
1927
  </section>
1448
1928
 
1449
1929
  <section
1450
1930
  data-slot="table-configurator-initial-state"
1451
- class="rounded-lg border border-zinc-200 p-4"
1931
+ class="border-t border-zinc-200 pt-4"
1452
1932
  >
1453
1933
  <h4 class="text-sm font-semibold text-zinc-800">
1454
1934
  {{ t("initial-state") }}
1455
1935
  </h4>
1456
1936
 
1457
- <div class="mt-3 flex flex-col gap-4">
1937
+ <div class="mt-4 grid gap-3 xl:grid-cols-3">
1458
1938
  <section
1459
1939
  data-slot="table-configurator-initial-state-left"
1460
- class="rounded-md border border-zinc-200/80 p-3"
1940
+ class="flex flex-col gap-3 rounded-md bg-zinc-50/80 px-4 py-3"
1461
1941
  >
1462
1942
  <p class="text-xs font-medium text-zinc-500">
1463
1943
  {{ t("pinned-left-group") }}
1464
1944
  </p>
1465
1945
 
1466
1946
  <div
1467
- v-if="leftPinnedInitialStateItems.length > 0"
1468
1947
  ref="leftPinnedSortableListRef"
1469
- class="mt-3 flex flex-col gap-1"
1948
+ data-slot="table-configurator-initial-state-left-list"
1949
+ class="mt-3 flex min-h-14 flex-col gap-1 rounded-md border border-dashed border-zinc-200/80 p-1"
1470
1950
  >
1471
1951
  <div
1472
1952
  v-for="item in leftPinnedInitialStateItems"
1473
1953
  :key="item.itemId"
1474
1954
  data-slot="table-configurator-initial-state-left-item"
1475
- class="flex items-center gap-2 rounded-md border border-transparent p-1"
1955
+ class="flex cursor-grab items-center gap-2 rounded-md border border-transparent p-1 text-left active:cursor-grabbing"
1956
+ :title="t('drag-initial-state-column', { column: item.label })"
1476
1957
  >
1477
- <button
1478
- type="button"
1958
+ <div
1479
1959
  data-slot="table-configurator-initial-state-left-drag-handle"
1480
- class="flex size-8 shrink-0 cursor-grab items-center justify-center rounded-sm text-zinc-400 active:cursor-grabbing"
1481
- :aria-label="t('drag-left-column', { column: item.label })"
1960
+ class="flex size-8 shrink-0 items-center justify-center rounded-sm text-zinc-400"
1961
+ aria-hidden="true"
1482
1962
  >
1483
1963
  <Icon icon="fluent:re-order-dots-vertical-20-regular" />
1484
- </button>
1964
+ </div>
1485
1965
 
1486
1966
  <span class="min-w-0 flex-1 truncate px-1 text-sm font-medium text-zinc-800">{{ item.label }}</span>
1487
-
1488
- <button
1489
- v-if="item.canManage"
1490
- type="button"
1491
- data-slot="table-configurator-unpin-from-left"
1492
- class="flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-700"
1493
- :aria-label="t('unpin-left-column', { column: item.label })"
1494
- @click="moveRootItemToGroup(item.itemId, 'center')"
1495
- >
1496
- <Icon icon="fluent:padding-right-20-regular" />
1497
- </button>
1498
1967
  </div>
1499
- </div>
1500
1968
 
1501
- <p
1502
- v-else
1503
- data-slot="table-configurator-initial-state-left-empty"
1504
- class="mt-3 text-xs text-zinc-400"
1505
- >
1506
- {{ t("no-pinned-left-columns") }}
1507
- </p>
1969
+ <p
1970
+ v-if="leftPinnedInitialStateItems.length === 0"
1971
+ data-slot="table-configurator-initial-state-left-empty"
1972
+ class="p-2 text-xs text-zinc-400"
1973
+ >
1974
+ {{ t("no-pinned-left-columns") }}
1975
+ </p>
1976
+ </div>
1508
1977
  </section>
1509
1978
 
1510
1979
  <section
1511
1980
  data-slot="table-configurator-initial-state-center"
1512
- class="rounded-md border border-zinc-200/80 p-3"
1981
+ class="flex flex-col gap-3 rounded-md bg-zinc-50/80 px-4 py-3"
1513
1982
  >
1514
1983
  <p class="text-xs font-medium text-zinc-500">
1515
1984
  {{ t("pinned-center-group") }}
1516
1985
  </p>
1517
1986
 
1518
1987
  <div
1519
- v-if="centerInitialStateItems.length > 0"
1520
1988
  ref="centerSortableListRef"
1521
- class="mt-3 flex flex-col gap-1"
1989
+ data-slot="table-configurator-initial-state-center-list"
1990
+ class="mt-3 flex min-h-14 flex-col gap-1 rounded-md border border-dashed border-zinc-200/80 p-1"
1522
1991
  >
1523
1992
  <div
1524
1993
  v-for="item in centerInitialStateItems"
1525
1994
  :key="item.itemId"
1526
1995
  data-slot="table-configurator-initial-state-center-item"
1527
- class="flex items-center gap-2 rounded-md border border-transparent p-1"
1996
+ class="flex cursor-grab items-center gap-2 rounded-md border border-transparent p-1 text-left active:cursor-grabbing"
1997
+ :title="t('drag-initial-state-column', { column: item.label })"
1528
1998
  >
1529
- <button
1530
- type="button"
1999
+ <div
1531
2000
  data-slot="table-configurator-initial-state-center-drag-handle"
1532
- class="flex size-8 shrink-0 cursor-grab items-center justify-center rounded-sm text-zinc-400 active:cursor-grabbing"
1533
- :aria-label="t('drag-center-column', { column: item.label })"
2001
+ class="flex size-8 shrink-0 items-center justify-center rounded-sm text-zinc-400"
2002
+ aria-hidden="true"
1534
2003
  >
1535
2004
  <Icon icon="fluent:re-order-dots-vertical-20-regular" />
1536
- </button>
2005
+ </div>
1537
2006
 
1538
2007
  <span class="min-w-0 flex-1 truncate px-1 text-sm font-medium text-zinc-800">{{ item.label }}</span>
1539
-
1540
- <div
1541
- v-if="item.canManage"
1542
- class="flex items-center gap-1"
1543
- >
1544
- <button
1545
- type="button"
1546
- data-slot="table-configurator-pin-left"
1547
- class="flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-700"
1548
- :aria-label="t('pin-left-column', { column: item.label })"
1549
- @click="moveRootItemToGroup(item.itemId, 'left')"
1550
- >
1551
- <Icon icon="fluent:padding-left-20-regular" />
1552
- </button>
1553
-
1554
- <button
1555
- type="button"
1556
- data-slot="table-configurator-pin-right"
1557
- class="flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-700"
1558
- :aria-label="t('pin-right-column', { column: item.label })"
1559
- @click="moveRootItemToGroup(item.itemId, 'right')"
1560
- >
1561
- <Icon icon="fluent:padding-right-20-regular" />
1562
- </button>
1563
- </div>
1564
2008
  </div>
1565
- </div>
1566
2009
 
1567
- <p
1568
- v-else
1569
- data-slot="table-configurator-initial-state-center-empty"
1570
- class="mt-3 text-xs text-zinc-400"
1571
- >
1572
- {{ t("no-pinned-center-columns") }}
1573
- </p>
2010
+ <p
2011
+ v-if="centerInitialStateItems.length === 0"
2012
+ data-slot="table-configurator-initial-state-center-empty"
2013
+ class="p-2 text-xs text-zinc-400"
2014
+ >
2015
+ {{ t("no-pinned-center-columns") }}
2016
+ </p>
2017
+ </div>
1574
2018
  </section>
1575
2019
 
1576
2020
  <section
1577
2021
  data-slot="table-configurator-initial-state-right"
1578
- class="rounded-md border border-zinc-200/80 p-3"
2022
+ class="flex flex-col gap-3 rounded-md bg-zinc-50/80 px-4 py-3"
1579
2023
  >
1580
2024
  <p class="text-xs font-medium text-zinc-500">
1581
2025
  {{ t("pinned-right-group") }}
1582
2026
  </p>
1583
2027
 
1584
2028
  <div
1585
- v-if="rightPinnedInitialStateItems.length > 0"
1586
2029
  ref="rightPinnedSortableListRef"
1587
- class="mt-3 flex flex-col gap-1"
2030
+ data-slot="table-configurator-initial-state-right-list"
2031
+ class="mt-3 flex min-h-14 flex-col gap-1 rounded-md border border-dashed border-zinc-200/80 p-1"
1588
2032
  >
1589
2033
  <div
1590
2034
  v-for="item in rightPinnedInitialStateItems"
1591
2035
  :key="item.itemId"
1592
2036
  data-slot="table-configurator-initial-state-right-item"
1593
- class="flex items-center gap-2 rounded-md border border-transparent p-1"
2037
+ class="flex cursor-grab items-center gap-2 rounded-md border border-transparent p-1 text-left active:cursor-grabbing"
2038
+ :title="t('drag-initial-state-column', { column: item.label })"
1594
2039
  >
1595
- <button
1596
- type="button"
2040
+ <div
1597
2041
  data-slot="table-configurator-initial-state-right-drag-handle"
1598
- class="flex size-8 shrink-0 cursor-grab items-center justify-center rounded-sm text-zinc-400 active:cursor-grabbing"
1599
- :aria-label="t('drag-right-column', { column: item.label })"
2042
+ class="flex size-8 shrink-0 items-center justify-center rounded-sm text-zinc-400"
2043
+ aria-hidden="true"
1600
2044
  >
1601
2045
  <Icon icon="fluent:re-order-dots-vertical-20-regular" />
1602
- </button>
2046
+ </div>
1603
2047
 
1604
2048
  <span class="min-w-0 flex-1 truncate px-1 text-sm font-medium text-zinc-800">{{ item.label }}</span>
1605
-
1606
- <button
1607
- v-if="item.canManage"
1608
- type="button"
1609
- data-slot="table-configurator-unpin-from-right"
1610
- class="flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-700"
1611
- :aria-label="t('unpin-right-column', { column: item.label })"
1612
- @click="moveRootItemToGroup(item.itemId, 'center')"
1613
- >
1614
- <Icon icon="fluent:padding-left-20-regular" />
1615
- </button>
1616
2049
  </div>
1617
- </div>
1618
2050
 
1619
- <p
1620
- v-else
1621
- data-slot="table-configurator-initial-state-right-empty"
1622
- class="mt-3 text-xs text-zinc-400"
1623
- >
1624
- {{ t("no-pinned-right-columns") }}
1625
- </p>
2051
+ <p
2052
+ v-if="rightPinnedInitialStateItems.length === 0"
2053
+ data-slot="table-configurator-initial-state-right-empty"
2054
+ class="p-2 text-xs text-zinc-400"
2055
+ >
2056
+ {{ t("no-pinned-right-columns") }}
2057
+ </p>
2058
+ </div>
1626
2059
  </section>
1627
2060
  </div>
1628
2061
  </section>
@@ -1674,6 +2107,7 @@ function confirmChanges() {
1674
2107
 
1675
2108
  <div class="flex h-full items-stretch border-l border-zinc-200">
1676
2109
  <Toggle
2110
+ data-field-key="columnGrow"
1677
2111
  class="h-full rounded-none border-0 px-3 text-xs shadow-none"
1678
2112
  :model-value="selectedColumn.grow ?? false"
1679
2113
  @update:model-value="updateSelectedColumnGrow"
@@ -1685,7 +2119,10 @@ function confirmChanges() {
1685
2119
  </InputGroup>
1686
2120
  </label>
1687
2121
 
1688
- <label class="flex items-center justify-between gap-3 py-1">
2122
+ <label
2123
+ data-field-key="columnEnableSorting"
2124
+ class="flex items-center justify-between gap-3 py-1"
2125
+ >
1689
2126
  <div class="flex flex-col gap-1">
1690
2127
  <span class="text-sm font-medium text-zinc-800">{{ t("column-enable-sorting") }}</span>
1691
2128
  <span class="text-xs text-zinc-500">{{ t("column-enable-sorting-description") }}</span>
@@ -1938,34 +2375,50 @@ function confirmChanges() {
1938
2375
  </section>
1939
2376
  </div>
1940
2377
 
1941
- <DialogFooter class="border-t border-zinc-200 px-6 py-4">
1942
- <Button
1943
- type="button"
1944
- data-slot="table-configurator-export"
1945
- variant="ghost"
1946
- @click="exportConfig"
1947
- >
1948
- <Icon icon="fluent:arrow-download-20-regular" />
1949
- {{ t("export-config") }}
1950
- </Button>
1951
- <Button
1952
- type="button"
1953
- data-slot="table-configurator-cancel"
1954
- variant="default"
1955
- @click="discardChanges"
2378
+ <DialogFooter class="border-t border-zinc-200 px-6 py-4 sm:justify-between">
2379
+ <div
2380
+ data-slot="table-configurator-copy-actions"
2381
+ class="flex items-center gap-2"
1956
2382
  >
1957
- <Icon icon="fluent:dismiss-20-regular" />
1958
- {{ t("cancel") }}
1959
- </Button>
1960
- <Button
1961
- type="button"
1962
- data-slot="table-configurator-confirm"
1963
- variant="primary"
1964
- @click="confirmChanges"
1965
- >
1966
- <Icon icon="fluent:checkmark-20-regular" />
1967
- {{ t("confirm") }}
1968
- </Button>
2383
+ <Button
2384
+ type="button"
2385
+ data-slot="table-configurator-copy-markdown"
2386
+ variant="ghost"
2387
+ @click="copyMarkdown"
2388
+ >
2389
+ <Icon icon="simple-icons:markdown" />
2390
+ {{ t("copy-markdown") }}
2391
+ </Button>
2392
+ <Button
2393
+ type="button"
2394
+ data-slot="table-configurator-copy-config"
2395
+ variant="ghost"
2396
+ @click="copyConfig"
2397
+ >
2398
+ <Icon icon="fluent:copy-20-regular" />
2399
+ {{ t("copy-config") }}
2400
+ </Button>
2401
+ </div>
2402
+ <div class="flex items-center gap-2">
2403
+ <Button
2404
+ type="button"
2405
+ data-slot="table-configurator-cancel"
2406
+ variant="default"
2407
+ @click="discardChanges"
2408
+ >
2409
+ <Icon icon="fluent:dismiss-20-regular" />
2410
+ {{ t("cancel") }}
2411
+ </Button>
2412
+ <Button
2413
+ type="button"
2414
+ data-slot="table-configurator-confirm"
2415
+ variant="primary"
2416
+ @click="confirmChanges"
2417
+ >
2418
+ <Icon icon="fluent:checkmark-20-regular" />
2419
+ {{ t("confirm") }}
2420
+ </Button>
2421
+ </div>
1969
2422
  </DialogFooter>
1970
2423
  </DialogContent>
1971
2424
  </Dialog>
@@ -1976,11 +2429,16 @@ function confirmChanges() {
1976
2429
  "zh": {
1977
2430
  "configure-table": "配置表格",
1978
2431
  "configure-table-description": "在这里浏览表格和列配置项。",
1979
- "import-config": "导入",
1980
- "export-config": "导出",
1981
- "import-config-invalid-json": "导入失败,文件不是有效的 JSON。",
1982
- "import-config-invalid-schema": "导入失败,配置格式无效。",
1983
- "export-config-failed": "导出失败,请先修正当前配置中的错误。",
2432
+ "paste-config": "粘贴配置",
2433
+ "paste-config-invalid-json": "粘贴失败,剪贴板不是有效的 JSON。",
2434
+ "paste-config-invalid-schema": "粘贴失败,配置格式无效。",
2435
+ "paste-config-read-failed": "读取剪贴板失败,请检查剪贴板权限。",
2436
+ "copy-paste-error": "复制错误",
2437
+ "copy-paste-error-failed": "复制错误详情失败,请检查剪贴板权限。",
2438
+ "copy-markdown": "复制为 Markdown",
2439
+ "copy-markdown-failed": "复制 Markdown 失败,请先修正当前配置中的错误。",
2440
+ "copy-config": "仅复制配置",
2441
+ "copy-config-failed": "复制配置失败,请先修正当前配置中的错误。",
1984
2442
  "search-columns": "搜索列名称……",
1985
2443
  "general": "通用",
1986
2444
  "no-matches": "没有匹配的列。",
@@ -2016,6 +2474,11 @@ function confirmChanges() {
2016
2474
  "pagination-right": "右侧文案",
2017
2475
  "pagination-right-placeholder": "输入分页右侧 Markdown",
2018
2476
  "pagination-page-size": "每页条数",
2477
+ "pagination-page-size-options": "每页条数选项",
2478
+ "add-pagination-page-size": "新增选项",
2479
+ "drag-pagination-page-size": "拖拽调整每页条数选项顺序",
2480
+ "remove-pagination-page-size": "删除每页条数选项",
2481
+ "no-pagination-page-sizes": "暂未配置每页条数选项。",
2019
2482
  "initial-state": "初始状态",
2020
2483
  "column-options": "列配置",
2021
2484
  "column-main": "基本",
@@ -2047,13 +2510,7 @@ function confirmChanges() {
2047
2510
  "no-selected-children": "当前列还没有子列。",
2048
2511
  "no-available-columns": "没有可添加的列。",
2049
2512
  "add-column": "新增列",
2050
- "drag-left-column": "拖拽调整左侧组顺序:{column}",
2051
- "drag-center-column": "拖拽调整中间组顺序:{column}",
2052
- "drag-right-column": "拖拽调整右侧组顺序:{column}",
2053
- "pin-left-column": "固定到左侧组:{column}",
2054
- "pin-right-column": "固定到右侧组:{column}",
2055
- "unpin-left-column": "从左侧组释放到中间组:{column}",
2056
- "unpin-right-column": "从右侧组释放到中间组:{column}",
2513
+ "drag-initial-state-column": "拖拽调整列分组和顺序:{column}",
2057
2514
  "drag-child-column": "拖拽调整子列顺序:{column}",
2058
2515
  "delete-column": "删除列:{column}",
2059
2516
  "remove-child-column": "移出子列:{column}",
@@ -2065,6 +2522,16 @@ function confirmChanges() {
2065
2522
  "ja": {
2066
2523
  "configure-table": "テーブルを設定",
2067
2524
  "configure-table-description": "ここではテーブルと列の設定項目を確認できます。",
2525
+ "paste-config": "設定を貼り付け",
2526
+ "paste-config-invalid-json": "貼り付けに失敗しました。クリップボードの内容が有効な JSON ではありません。",
2527
+ "paste-config-invalid-schema": "貼り付けに失敗しました。設定形式が無効です。",
2528
+ "paste-config-read-failed": "クリップボードの読み取りに失敗しました。権限を確認してください。",
2529
+ "copy-paste-error": "エラーをコピー",
2530
+ "copy-paste-error-failed": "エラー詳細のコピーに失敗しました。クリップボード権限を確認してください。",
2531
+ "copy-markdown": "Markdown としてコピー",
2532
+ "copy-markdown-failed": "Markdown のコピーに失敗しました。現在の設定エラーを先に修正してください。",
2533
+ "copy-config": "設定のみコピー",
2534
+ "copy-config-failed": "設定のコピーに失敗しました。現在の設定エラーを先に修正してください。",
2068
2535
  "search-columns": "列名を検索…",
2069
2536
  "general": "共通",
2070
2537
  "no-matches": "一致する列がありません。",
@@ -2100,6 +2567,11 @@ function confirmChanges() {
2100
2567
  "pagination-right": "右側テキスト",
2101
2568
  "pagination-right-placeholder": "右側の Markdown を入力",
2102
2569
  "pagination-page-size": "1ページあたりの件数",
2570
+ "pagination-page-size-options": "1ページあたりの件数候補",
2571
+ "add-pagination-page-size": "候補を追加",
2572
+ "drag-pagination-page-size": "ドラッグして1ページあたりの件数候補の順序を変更",
2573
+ "remove-pagination-page-size": "1ページあたりの件数候補を削除",
2574
+ "no-pagination-page-sizes": "1ページあたりの件数候補はまだありません。",
2103
2575
  "initial-state": "初期状態",
2104
2576
  "column-options": "列設定",
2105
2577
  "column-main": "基本",
@@ -2131,13 +2603,7 @@ function confirmChanges() {
2131
2603
  "no-selected-children": "この列にはまだ子列がありません。",
2132
2604
  "no-available-columns": "追加できる列がありません。",
2133
2605
  "add-column": "列を追加",
2134
- "drag-left-column": "{column} の左側グループ順をドラッグで変更",
2135
- "drag-center-column": "{column} の中央グループ順をドラッグで変更",
2136
- "drag-right-column": "{column} の右側グループ順をドラッグで変更",
2137
- "pin-left-column": "{column} を左側グループに固定",
2138
- "pin-right-column": "{column} を右側グループに固定",
2139
- "unpin-left-column": "{column} を左側グループから中央グループに戻す",
2140
- "unpin-right-column": "{column} を右側グループから中央グループに戻す",
2606
+ "drag-initial-state-column": "{column} の列グループと順序をドラッグで変更",
2141
2607
  "drag-child-column": "{column} の子列順をドラッグで変更",
2142
2608
  "delete-column": "{column} を削除",
2143
2609
  "remove-child-column": "{column} を子列から外す",
@@ -2149,6 +2615,16 @@ function confirmChanges() {
2149
2615
  "en": {
2150
2616
  "configure-table": "Configure Table",
2151
2617
  "configure-table-description": "Browse the table and column settings here.",
2618
+ "paste-config": "Paste Config",
2619
+ "paste-config-invalid-json": "Paste failed because the clipboard does not contain valid JSON.",
2620
+ "paste-config-invalid-schema": "Paste failed because the config shape is invalid.",
2621
+ "paste-config-read-failed": "Failed to read from the clipboard. Check clipboard permissions.",
2622
+ "copy-paste-error": "Copy Error",
2623
+ "copy-paste-error-failed": "Failed to copy the error details. Check clipboard permissions.",
2624
+ "copy-markdown": "Copy as Markdown",
2625
+ "copy-markdown-failed": "Failed to copy Markdown. Fix the current config errors first.",
2626
+ "copy-config": "Copy Config Only",
2627
+ "copy-config-failed": "Failed to copy the config. Fix the current config errors first.",
2152
2628
  "search-columns": "Search columns…",
2153
2629
  "general": "General",
2154
2630
  "no-matches": "No matching columns.",
@@ -2184,6 +2660,11 @@ function confirmChanges() {
2184
2660
  "pagination-right": "Right content",
2185
2661
  "pagination-right-placeholder": "Enter right pagination markdown",
2186
2662
  "pagination-page-size": "Page size",
2663
+ "pagination-page-size-options": "Page size options",
2664
+ "add-pagination-page-size": "Add option",
2665
+ "drag-pagination-page-size": "Drag to reorder page size options",
2666
+ "remove-pagination-page-size": "Remove page size option",
2667
+ "no-pagination-page-sizes": "No page size options configured.",
2187
2668
  "initial-state": "Initial State",
2188
2669
  "column-options": "Column options",
2189
2670
  "column-main": "Basics",
@@ -2215,13 +2696,7 @@ function confirmChanges() {
2215
2696
  "no-selected-children": "This column has no child columns yet.",
2216
2697
  "no-available-columns": "No columns can be added here.",
2217
2698
  "add-column": "Add column",
2218
- "drag-left-column": "Drag to reorder left group column {column}",
2219
- "drag-center-column": "Drag to reorder center group column {column}",
2220
- "drag-right-column": "Drag to reorder right group column {column}",
2221
- "pin-left-column": "Pin {column} to the left group",
2222
- "pin-right-column": "Pin {column} to the right group",
2223
- "unpin-left-column": "Move {column} from the left group to the center group",
2224
- "unpin-right-column": "Move {column} from the right group to the center group",
2699
+ "drag-initial-state-column": "Drag to move and reorder column {column}",
2225
2700
  "drag-child-column": "Drag to reorder child column {column}",
2226
2701
  "delete-column": "Delete {column}",
2227
2702
  "remove-child-column": "Remove child column {column}",