@dumiflex/wp-kit 0.3.0 → 0.5.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 (130) hide show
  1. package/CHANGELOG.md +92 -51
  2. package/LICENSE +674 -674
  3. package/README.md +73 -76
  4. package/package.json +78 -77
  5. package/src/Avatar.vue +83 -83
  6. package/src/Badge.vue +97 -97
  7. package/src/Banner.vue +118 -118
  8. package/src/BookmarkButton.vue +27 -27
  9. package/src/BundleMeta.vue +51 -51
  10. package/src/Button.vue +103 -103
  11. package/src/Card.vue +68 -68
  12. package/src/Checkbox.vue +109 -107
  13. package/src/Chip.vue +163 -97
  14. package/src/ChoiceCards.vue +133 -133
  15. package/src/CloseButton.vue +69 -69
  16. package/src/CodeBlock.vue +146 -119
  17. package/src/ColorField.vue +188 -206
  18. package/src/ColorPicker.vue +201 -201
  19. package/src/Combobox.vue +331 -334
  20. package/src/ConfirmDialog.vue +165 -163
  21. package/src/ConfirmModal.vue +147 -147
  22. package/src/ConstraintMatrixPreview.vue +708 -708
  23. package/src/ContextMenu.vue +386 -379
  24. package/src/CopyButton.vue +156 -156
  25. package/src/DataTable.vue +293 -292
  26. package/src/DatePicker.vue +234 -234
  27. package/src/DescriptionList.vue +73 -73
  28. package/src/DiffView.vue +68 -68
  29. package/src/Disclosure.vue +166 -166
  30. package/src/DragHandle.vue +49 -49
  31. package/src/Drawer.vue +131 -131
  32. package/src/DropZone.vue +196 -196
  33. package/src/DropdownMenu.vue +72 -72
  34. package/src/EmptyState.vue +111 -111
  35. package/src/Field.vue +28 -28
  36. package/src/HsvPicker.vue +208 -208
  37. package/src/Icon.vue +39 -39
  38. package/src/IconPicker.vue +209 -209
  39. package/src/ImageGrid.vue +127 -125
  40. package/src/InlineEdit.vue +183 -183
  41. package/src/Input.vue +99 -99
  42. package/src/Kbd.vue +56 -56
  43. package/src/Lightbox.vue +176 -173
  44. package/src/ListFilter.vue +85 -85
  45. package/src/ListRow.vue +204 -204
  46. package/src/LoadingMark.vue +217 -161
  47. package/src/Logo.vue +60 -60
  48. package/src/Modal.vue +78 -76
  49. package/src/ModalShell.vue +109 -107
  50. package/src/Pagination.vue +106 -106
  51. package/src/Popover.vue +180 -171
  52. package/src/PostCard.vue +210 -211
  53. package/src/ProgressBar.vue +90 -90
  54. package/src/RadioGroup.vue +177 -177
  55. package/src/RangeSlider.vue +183 -183
  56. package/src/RelativeDate.vue +35 -35
  57. package/src/ResizeHandle.vue +143 -143
  58. package/src/SearchInput.vue +192 -199
  59. package/src/Select.vue +644 -641
  60. package/src/Separator.vue +54 -54
  61. package/src/Skeleton.vue +98 -82
  62. package/src/Slider.vue +198 -198
  63. package/src/SortableList.vue +260 -260
  64. package/src/SplitPane.vue +68 -68
  65. package/src/StarButton.vue +40 -40
  66. package/src/Stepper.vue +141 -141
  67. package/src/SupportLinks.vue +93 -93
  68. package/src/TagInput.vue +297 -300
  69. package/src/Textarea.vue +57 -57
  70. package/src/Timeline.vue +92 -92
  71. package/src/ToastCard.vue +119 -119
  72. package/src/ToastHost.vue +40 -40
  73. package/src/Toggle.vue +36 -36
  74. package/src/TokenizedValue.vue +310 -310
  75. package/src/Toolbar.vue +110 -110
  76. package/src/VirtualList.vue +87 -87
  77. package/src/VisuallyHidden.vue +29 -29
  78. package/src/WpButton.vue +75 -75
  79. package/src/WpCheck.vue +104 -102
  80. package/src/WpIconButton.vue +107 -107
  81. package/src/WpImageZoom.vue +544 -544
  82. package/src/WpNumber.vue +178 -183
  83. package/src/WpSegmented.vue +175 -175
  84. package/src/WpSelect.vue +93 -89
  85. package/src/WpSwitch.vue +163 -163
  86. package/src/WpTabs.vue +188 -188
  87. package/src/_modal-motion.css +33 -33
  88. package/src/_skeleton.css +50 -50
  89. package/src/axis-color.ts +42 -42
  90. package/src/button.css +169 -169
  91. package/src/calendar.ts +59 -59
  92. package/src/clipboard.ts +50 -50
  93. package/src/colour-presets.ts +12 -12
  94. package/src/diff.ts +51 -51
  95. package/src/drop-zone.ts +41 -41
  96. package/src/field.css +50 -0
  97. package/src/flip.ts +237 -237
  98. package/src/floating-layer.ts +48 -0
  99. package/src/floating.ts +99 -99
  100. package/src/highlight.ts +199 -0
  101. package/src/hsv.ts +100 -100
  102. package/src/icon-catalog.ts +52 -52
  103. package/src/image-zoom.ts +93 -93
  104. package/src/kind-colour.ts +28 -0
  105. package/src/loading-mark-motion.ts +249 -0
  106. package/src/logo-paths.ts +21 -21
  107. package/src/matrix-axis.ts +91 -91
  108. package/src/pagination.ts +27 -27
  109. package/src/platform-keys.ts +29 -29
  110. package/src/rich-tokenize.ts +357 -357
  111. package/src/select-types.ts +32 -32
  112. package/src/slider-math.ts +45 -45
  113. package/src/sortable.ts +22 -22
  114. package/src/theme/fonts/OFL-Fraunces.txt +93 -93
  115. package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -93
  116. package/src/theme/fonts/OFL-Manrope.txt +93 -93
  117. package/src/theme/looks.css +428 -428
  118. package/src/theme/palettes.css +254 -254
  119. package/src/theme/tokens.css +2896 -2894
  120. package/src/tokens.ts +56 -56
  121. package/src/tooltip.css +43 -43
  122. package/src/tooltip.ts +146 -146
  123. package/src/useDialogFocus.ts +73 -73
  124. package/src/useGrowableField.ts +357 -357
  125. package/src/useModalKeyShield.ts +116 -116
  126. package/src/useToast.ts +48 -48
  127. package/src/wp-logo.svg +14 -14
  128. package/src/ChipToggle.vue +0 -92
  129. package/src/KindChip.vue +0 -53
  130. package/src/RatingBadge.vue +0 -42
package/src/DiffView.vue CHANGED
@@ -1,68 +1,68 @@
1
- <script setup lang="ts">
2
- /**
3
- * Diff view: what changed between two texts, word by word (an option, a
4
- * template) or line by line (a pack, a log). Removed text is struck through
5
- * on red, added text is on green, and each change also says "removed" /
6
- * "added" to screen readers, so colour is never the only sign. Works on the
7
- * canvas and in the manager.
8
- *
9
- * <DiffView before="auburn hair, rim light" after="silver hair, rim light" />
10
- * <DiffView :before="oldJson" :after="newJson" by="lines" layout="split" />
11
- *
12
- * `layout="split"` shows Before and After side by side. Very long texts fall
13
- * back to "all removed, all added" rather than freeze the page.
14
- */
15
- import { computed } from "vue";
16
- import { diffText } from "./diff";
17
-
18
- const props = withDefaults(defineProps<{
19
- before: string;
20
- after: string;
21
- by?: "words" | "lines" | undefined;
22
- layout?: "inline" | "split" | undefined;
23
- size?: "sm" | "md" | undefined;
24
- /** Mono font, for code and JSON. */
25
- mono?: boolean | undefined;
26
- }>(), { by: "words", layout: "inline", size: "md" });
27
-
28
- const parts = computed(() => diffText(props.before, props.after, props.by));
29
- const counts = computed(() => ({
30
- add: parts.value.filter((p) => p.kind === "add").length,
31
- del: parts.value.filter((p) => p.kind === "del").length,
32
- }));
33
- </script>
34
-
35
- <template>
36
- <div class="wp-diff" :data-size="size" :data-layout="layout" :data-by="by" :data-mono="mono || by === 'lines' || undefined">
37
- <p v-if="!counts.add && !counts.del" class="wp-diff__same">No changes</p>
38
- <template v-else-if="layout === 'inline'">
39
- <div class="wp-diff__text"><template v-for="(p, i) in parts" :key="i"><span v-if="p.kind === 'same'">{{ p.text }}</span><del v-else-if="p.kind === 'del'" class="wp-diff__del"><span class="wp-diff__sr">removed: </span>{{ p.text }}</del><ins v-else class="wp-diff__add"><span class="wp-diff__sr">added: </span>{{ p.text }}</ins></template></div>
40
- </template>
41
- <div v-else class="wp-diff__split">
42
- <section class="wp-diff__side" aria-label="Before">
43
- <span class="wp-diff__side-label" aria-hidden="true">Before</span>
44
- <div class="wp-diff__text"><template v-for="(p, i) in parts" :key="i"><span v-if="p.kind === 'same'">{{ p.text }}</span><del v-else-if="p.kind === 'del'" class="wp-diff__del"><span class="wp-diff__sr">removed: </span>{{ p.text }}</del></template></div>
45
- </section>
46
- <section class="wp-diff__side" aria-label="After">
47
- <span class="wp-diff__side-label" aria-hidden="true">After</span>
48
- <div class="wp-diff__text"><template v-for="(p, i) in parts" :key="i"><span v-if="p.kind === 'same'">{{ p.text }}</span><ins v-else-if="p.kind === 'add'" class="wp-diff__add"><span class="wp-diff__sr">added: </span>{{ p.text }}</ins></template></div>
49
- </section>
50
- </div>
51
- </div>
52
- </template>
53
-
54
- <style scoped>
55
- .wp-diff { min-width: 0; color: var(--wp-text); font: 400 var(--wp-text-sm)/1.6 var(--wp-font-sans); }
56
- .wp-diff[data-size="sm"] { font-size: var(--wp-text-xs); }
57
- .wp-diff[data-mono] { font-family: var(--wp-font-mono); }
58
- .wp-diff__text { white-space: pre-wrap; overflow-wrap: anywhere; }
59
- .wp-diff__del, .wp-diff__add { border-radius: min(3px, var(--wp-radius-cap, 999px)); padding: 0 1px; text-decoration-thickness: 1px; }
60
- .wp-diff__del { background: color-mix(in srgb, var(--wp-danger) 16%, transparent); color: var(--wp-danger-text); text-decoration: line-through; text-decoration-color: color-mix(in srgb, var(--wp-danger) 70%, transparent); }
61
- .wp-diff__add { background: color-mix(in srgb, var(--wp-success) 18%, transparent); color: var(--wp-text); text-decoration: none; box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--wp-success) 60%, transparent); }
62
- .wp-diff[data-by="lines"] .wp-diff__del, .wp-diff[data-by="lines"] .wp-diff__add { display: block; padding: 0 var(--wp-space-2); border-radius: 0; }
63
- .wp-diff__split { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--wp-space-4); }
64
- .wp-diff__side { min-width: 0; padding: var(--wp-space-3) var(--wp-space-4); border: 1px solid var(--wp-border); border-radius: var(--wp-radius); background: var(--wp-bg-1); }
65
- .wp-diff__side-label { display: block; margin-bottom: var(--wp-space-2); color: var(--wp-text-dim); font: 600 var(--wp-text-xs)/1 var(--wp-font-sans); letter-spacing: 0.06em; text-transform: uppercase; }
66
- .wp-diff__same { margin: 0; color: var(--wp-text-dim); font-style: italic; }
67
- .wp-diff__sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
68
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Diff view: what changed between two texts, word by word (an option, a
4
+ * template) or line by line (a pack, a log). Removed text is struck through
5
+ * on red, added text is on green, and each change also says "removed" /
6
+ * "added" to screen readers, so colour is never the only sign. Works on the
7
+ * canvas and in the manager.
8
+ *
9
+ * <DiffView before="auburn hair, rim light" after="silver hair, rim light" />
10
+ * <DiffView :before="oldJson" :after="newJson" by="lines" layout="split" />
11
+ *
12
+ * `layout="split"` shows Before and After side by side. Very long texts fall
13
+ * back to "all removed, all added" rather than freeze the page.
14
+ */
15
+ import { computed } from "vue";
16
+ import { diffText } from "./diff";
17
+
18
+ const props = withDefaults(defineProps<{
19
+ before: string;
20
+ after: string;
21
+ by?: "words" | "lines" | undefined;
22
+ layout?: "inline" | "split" | undefined;
23
+ size?: "sm" | "md" | undefined;
24
+ /** Mono font, for code and JSON. */
25
+ mono?: boolean | undefined;
26
+ }>(), { by: "words", layout: "inline", size: "md" });
27
+
28
+ const parts = computed(() => diffText(props.before, props.after, props.by));
29
+ const counts = computed(() => ({
30
+ add: parts.value.filter((p) => p.kind === "add").length,
31
+ del: parts.value.filter((p) => p.kind === "del").length,
32
+ }));
33
+ </script>
34
+
35
+ <template>
36
+ <div class="wp-diff" :data-size="size" :data-layout="layout" :data-by="by" :data-mono="mono || by === 'lines' || undefined">
37
+ <p v-if="!counts.add && !counts.del" class="wp-diff__same">No changes</p>
38
+ <template v-else-if="layout === 'inline'">
39
+ <div class="wp-diff__text"><template v-for="(p, i) in parts" :key="i"><span v-if="p.kind === 'same'">{{ p.text }}</span><del v-else-if="p.kind === 'del'" class="wp-diff__del"><span class="wp-diff__sr">removed: </span>{{ p.text }}</del><ins v-else class="wp-diff__add"><span class="wp-diff__sr">added: </span>{{ p.text }}</ins></template></div>
40
+ </template>
41
+ <div v-else class="wp-diff__split">
42
+ <section class="wp-diff__side" aria-label="Before">
43
+ <span class="wp-diff__side-label" aria-hidden="true">Before</span>
44
+ <div class="wp-diff__text"><template v-for="(p, i) in parts" :key="i"><span v-if="p.kind === 'same'">{{ p.text }}</span><del v-else-if="p.kind === 'del'" class="wp-diff__del"><span class="wp-diff__sr">removed: </span>{{ p.text }}</del></template></div>
45
+ </section>
46
+ <section class="wp-diff__side" aria-label="After">
47
+ <span class="wp-diff__side-label" aria-hidden="true">After</span>
48
+ <div class="wp-diff__text"><template v-for="(p, i) in parts" :key="i"><span v-if="p.kind === 'same'">{{ p.text }}</span><ins v-else-if="p.kind === 'add'" class="wp-diff__add"><span class="wp-diff__sr">added: </span>{{ p.text }}</ins></template></div>
49
+ </section>
50
+ </div>
51
+ </div>
52
+ </template>
53
+
54
+ <style scoped>
55
+ .wp-diff { min-width: 0; color: var(--wp-text); font: 400 var(--wp-text-sm)/1.6 var(--wp-font-sans); }
56
+ .wp-diff[data-size="sm"] { font-size: var(--wp-text-xs); }
57
+ .wp-diff[data-mono] { font-family: var(--wp-font-mono); }
58
+ .wp-diff__text { white-space: pre-wrap; overflow-wrap: anywhere; }
59
+ .wp-diff__del, .wp-diff__add { border-radius: min(3px, var(--wp-radius-cap, 999px)); padding: 0 1px; text-decoration-thickness: 1px; }
60
+ .wp-diff__del { background: color-mix(in srgb, var(--wp-danger) 16%, transparent); color: var(--wp-danger-text); text-decoration: line-through; text-decoration-color: color-mix(in srgb, var(--wp-danger) 70%, transparent); }
61
+ .wp-diff__add { background: color-mix(in srgb, var(--wp-success) 18%, transparent); color: var(--wp-text); text-decoration: none; box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--wp-success) 60%, transparent); }
62
+ .wp-diff[data-by="lines"] .wp-diff__del, .wp-diff[data-by="lines"] .wp-diff__add { display: block; padding: 0 var(--wp-space-2); border-radius: 0; }
63
+ .wp-diff__split { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--wp-space-4); }
64
+ .wp-diff__side { min-width: 0; padding: var(--wp-space-3) var(--wp-space-4); border: 1px solid var(--wp-border); border-radius: var(--wp-radius); background: var(--wp-bg-1); }
65
+ .wp-diff__side-label { display: block; margin-bottom: var(--wp-space-2); color: var(--wp-text-dim); font: 600 var(--wp-text-xs)/1 var(--wp-font-sans); letter-spacing: 0.06em; text-transform: uppercase; }
66
+ .wp-diff__same { margin: 0; color: var(--wp-text-dim); font-style: italic; }
67
+ .wp-diff__sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
68
+ </style>
@@ -1,166 +1,166 @@
1
- <script setup lang="ts">
2
- /**
3
- * Disclosure: a "show more" section that folds open under its heading. The
4
- * heading is a button with `aria-expanded` + `aria-controls` (APG
5
- * disclosure), the chevron turns when open, and the body grows to its height
6
- * (no motion with `prefers-reduced-motion`). Works on the canvas and in the
7
- * manager.
8
- *
9
- * <Disclosure v-model:open="open" label="Advanced">…</Disclosure>
10
- * <Disclosure label="Rules" :count="3"><template #summary>…</template>…</Disclosure>
11
- *
12
- * Leave out `v-model:open` and it keeps its own state (`default-open` sets
13
- * where it starts). `#actions` sits at the right end of the heading, outside
14
- * the button, for controls that should not fold the section; `#lead` sits
15
- * before the button for the same (a "select all" checkbox lined up with
16
- * the rows' own). `lazy` builds
17
- * the body only while it is open. `flush` makes the heading a full-width
18
- * band with a rule under it and drops the body's indent, for a section of a
19
- * table or list that fills its card (a group of options, a rule).
20
- */
21
- import { computed, ref, useId, useSlots } from "vue";
22
-
23
- const props = withDefaults(defineProps<{
24
- open?: boolean | undefined;
25
- /** Where it starts when nobody binds `open`. */
26
- defaultOpen?: boolean | undefined;
27
- label?: string | undefined;
28
- /** A small count after the label. */
29
- count?: number | string | undefined;
30
- size?: "sm" | "md" | undefined;
31
- disabled?: boolean | undefined;
32
- /** Draw a border and a padded body, for a section that stands alone. */
33
- boxed?: boolean | undefined;
34
- /** Full-width heading band, body without indent: a section of a table or list. */
35
- flush?: boolean | undefined;
36
- /** Build the body only while open (a long list, a costly panel). */
37
- lazy?: boolean | undefined;
38
- /** Extra attributes on the heading button, e.g. `data-test`. */
39
- triggerAttrs?: Record<string, string> | undefined;
40
- }>(), { open: undefined, size: "md", triggerAttrs: undefined });
41
-
42
- const emit = defineEmits<{ (e: "update:open", v: boolean): void }>();
43
- const slots = useSlots();
44
-
45
- const own = ref(props.defaultOpen);
46
- const isOpen = computed(() => props.open ?? own.value);
47
- const bodyId = `wp-disclosure-${useId()}`;
48
-
49
- function toggle(): void {
50
- if (props.disabled) return;
51
- const next = !isOpen.value;
52
- own.value = next;
53
- emit("update:open", next);
54
- }
55
- </script>
56
-
57
- <template>
58
- <div class="wp-disclosure" :data-size="size" :data-open="isOpen" :data-boxed="boxed || undefined" :data-flush="flush || undefined">
59
- <div class="wp-disclosure__head">
60
- <div v-if="slots.lead" class="wp-disclosure__lead"><slot name="lead" /></div>
61
- <button
62
- v-bind="triggerAttrs ?? {}"
63
- type="button"
64
- class="wp-disclosure__trigger"
65
- :aria-expanded="isOpen"
66
- :aria-controls="bodyId"
67
- :disabled="disabled"
68
- @click="toggle"
69
- >
70
- <i class="pi pi-chevron-right wp-disclosure__chev" aria-hidden="true" />
71
- <slot name="summary">
72
- <span class="wp-disclosure__label">{{ label }}</span>
73
- </slot>
74
- <span v-if="count !== undefined" class="wp-disclosure__count">{{ count }}</span>
75
- </button>
76
- <div v-if="slots.actions" class="wp-disclosure__actions"><slot name="actions" /></div>
77
- </div>
78
- <div :id="bodyId" class="wp-disclosure__body" :inert="!isOpen || undefined">
79
- <div class="wp-disclosure__inner">
80
- <div v-if="isOpen || !lazy" class="wp-disclosure__content"><slot /></div>
81
- </div>
82
- </div>
83
- </div>
84
- </template>
85
-
86
- <style scoped>
87
- .wp-disclosure { min-width: 0; }
88
- .wp-disclosure[data-boxed] {
89
- border: 1px solid var(--wp-border);
90
- border-radius: var(--wp-radius-md);
91
- background: var(--wp-bg-1);
92
- }
93
- .wp-disclosure__head { display: flex; align-items: center; gap: var(--wp-space-3); min-width: 0; }
94
- .wp-disclosure[data-boxed] .wp-disclosure__head { padding: 0 var(--wp-space-3); }
95
- .wp-disclosure__trigger {
96
- display: inline-flex;
97
- align-items: center;
98
- gap: var(--wp-space-3);
99
- flex: 1;
100
- min-width: 0;
101
- min-height: var(--wp-control-h-md);
102
- margin: 0;
103
- padding: 0 var(--wp-space-2);
104
- border: 0;
105
- border-radius: var(--wp-radius-sm);
106
- background: transparent;
107
- color: var(--wp-text);
108
- font: 600 var(--wp-text-sm)/1.3 var(--wp-font-sans);
109
- text-align: left;
110
- cursor: pointer;
111
- }
112
- .wp-disclosure[data-size="sm"] .wp-disclosure__trigger {
113
- min-height: var(--wp-control-h-sm);
114
- font-size: var(--wp-text-xs);
115
- gap: var(--wp-space-2);
116
- }
117
- .wp-disclosure__trigger:hover:not(:disabled) { background: var(--wp-row-hover); }
118
- .wp-disclosure__trigger:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--wp-border-focus); }
119
- .wp-disclosure__trigger:disabled { opacity: 0.45; cursor: not-allowed; }
120
- .wp-disclosure__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
121
- .wp-disclosure__chev {
122
- flex: none;
123
- font-size: 0.75em;
124
- color: var(--wp-text-muted);
125
- transition: transform var(--wp-motion-swap) var(--wp-motion-curve-collapse);
126
- }
127
- .wp-disclosure[data-open="true"] .wp-disclosure__chev { transform: rotate(90deg); }
128
- .wp-disclosure__count {
129
- padding: 1px 6px;
130
- border-radius: var(--wp-radius-cap, 999px);
131
- background: var(--wp-bg-3);
132
- color: var(--wp-text-muted);
133
- font-size: var(--wp-text-xs);
134
- font-weight: 500;
135
- font-variant-numeric: tabular-nums;
136
- }
137
- .wp-disclosure__lead { display: inline-flex; align-items: center; flex: none; }
138
- .wp-disclosure__actions { display: inline-flex; align-items: center; gap: var(--wp-space-2); flex: none; }
139
- /* Grid rows animate from 0fr to 1fr, so the body grows to its own height
140
- without measuring it. */
141
- .wp-disclosure__body {
142
- display: grid;
143
- grid-template-rows: 0fr;
144
- transition: grid-template-rows var(--wp-motion-swap) var(--wp-motion-curve-collapse);
145
- }
146
- .wp-disclosure[data-open="true"] > .wp-disclosure__body { grid-template-rows: 1fr; }
147
- .wp-disclosure__inner { min-height: 0; overflow: hidden; }
148
- .wp-disclosure__content { padding: var(--wp-space-3) 0 var(--wp-space-2) calc(var(--wp-space-2) + 1.1em + var(--wp-space-3)); }
149
- .wp-disclosure[data-size="sm"] .wp-disclosure__content { padding: var(--wp-space-2) 0 var(--wp-space-2) calc(var(--wp-space-2) + 1em); }
150
- .wp-disclosure[data-boxed] .wp-disclosure__content { padding: var(--wp-space-2) var(--wp-space-5) var(--wp-space-5); }
151
- .wp-disclosure[data-flush] > .wp-disclosure__head {
152
- border-bottom: 1px solid var(--wp-border);
153
- background: var(--wp-bg-2);
154
- }
155
- .wp-disclosure[data-flush] .wp-disclosure__trigger {
156
- padding: 0 var(--wp-space-5);
157
- border-radius: 0;
158
- font-size: var(--wp-text-xs);
159
- }
160
- .wp-disclosure[data-flush] .wp-disclosure__trigger:focus-visible { box-shadow: inset 0 0 0 2px var(--wp-border-focus); }
161
- .wp-disclosure[data-flush] .wp-disclosure__content,
162
- .wp-disclosure[data-flush][data-size="sm"] .wp-disclosure__content { padding: 0; }
163
- @media (prefers-reduced-motion: reduce) {
164
- .wp-disclosure__chev, .wp-disclosure__body { transition: none; }
165
- }
166
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Disclosure: a "show more" section that folds open under its heading. The
4
+ * heading is a button with `aria-expanded` + `aria-controls` (APG
5
+ * disclosure), the chevron turns when open, and the body grows to its height
6
+ * (no motion with `prefers-reduced-motion`). Works on the canvas and in the
7
+ * manager.
8
+ *
9
+ * <Disclosure v-model:open="open" label="Advanced">…</Disclosure>
10
+ * <Disclosure label="Rules" :count="3"><template #summary>…</template>…</Disclosure>
11
+ *
12
+ * Leave out `v-model:open` and it keeps its own state (`default-open` sets
13
+ * where it starts). `#actions` sits at the right end of the heading, outside
14
+ * the button, for controls that should not fold the section; `#lead` sits
15
+ * before the button for the same (a "select all" checkbox lined up with
16
+ * the rows' own). `lazy` builds
17
+ * the body only while it is open. `flush` makes the heading a full-width
18
+ * band with a rule under it and drops the body's indent, for a section of a
19
+ * table or list that fills its card (a group of options, a rule).
20
+ */
21
+ import { computed, ref, useId, useSlots } from "vue";
22
+
23
+ const props = withDefaults(defineProps<{
24
+ open?: boolean | undefined;
25
+ /** Where it starts when nobody binds `open`. */
26
+ defaultOpen?: boolean | undefined;
27
+ label?: string | undefined;
28
+ /** A small count after the label. */
29
+ count?: number | string | undefined;
30
+ size?: "sm" | "md" | undefined;
31
+ disabled?: boolean | undefined;
32
+ /** Draw a border and a padded body, for a section that stands alone. */
33
+ boxed?: boolean | undefined;
34
+ /** Full-width heading band, body without indent: a section of a table or list. */
35
+ flush?: boolean | undefined;
36
+ /** Build the body only while open (a long list, a costly panel). */
37
+ lazy?: boolean | undefined;
38
+ /** Extra attributes on the heading button, e.g. `data-test`. */
39
+ triggerAttrs?: Record<string, string> | undefined;
40
+ }>(), { open: undefined, size: "md", triggerAttrs: undefined });
41
+
42
+ const emit = defineEmits<{ (e: "update:open", v: boolean): void }>();
43
+ const slots = useSlots();
44
+
45
+ const own = ref(props.defaultOpen);
46
+ const isOpen = computed(() => props.open ?? own.value);
47
+ const bodyId = `wp-disclosure-${useId()}`;
48
+
49
+ function toggle(): void {
50
+ if (props.disabled) return;
51
+ const next = !isOpen.value;
52
+ own.value = next;
53
+ emit("update:open", next);
54
+ }
55
+ </script>
56
+
57
+ <template>
58
+ <div class="wp-disclosure" :data-size="size" :data-open="isOpen" :data-boxed="boxed || undefined" :data-flush="flush || undefined">
59
+ <div class="wp-disclosure__head">
60
+ <div v-if="slots.lead" class="wp-disclosure__lead"><slot name="lead" /></div>
61
+ <button
62
+ v-bind="triggerAttrs ?? {}"
63
+ type="button"
64
+ class="wp-disclosure__trigger"
65
+ :aria-expanded="isOpen"
66
+ :aria-controls="bodyId"
67
+ :disabled="disabled"
68
+ @click="toggle"
69
+ >
70
+ <i class="pi pi-chevron-right wp-disclosure__chev" aria-hidden="true" />
71
+ <slot name="summary">
72
+ <span class="wp-disclosure__label">{{ label }}</span>
73
+ </slot>
74
+ <span v-if="count !== undefined" class="wp-disclosure__count">{{ count }}</span>
75
+ </button>
76
+ <div v-if="slots.actions" class="wp-disclosure__actions"><slot name="actions" /></div>
77
+ </div>
78
+ <div :id="bodyId" class="wp-disclosure__body" :inert="!isOpen || undefined">
79
+ <div class="wp-disclosure__inner">
80
+ <div v-if="isOpen || !lazy" class="wp-disclosure__content"><slot /></div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </template>
85
+
86
+ <style scoped>
87
+ .wp-disclosure { min-width: 0; }
88
+ .wp-disclosure[data-boxed] {
89
+ border: 1px solid var(--wp-border);
90
+ border-radius: var(--wp-radius-md);
91
+ background: var(--wp-bg-1);
92
+ }
93
+ .wp-disclosure__head { display: flex; align-items: center; gap: var(--wp-space-3); min-width: 0; }
94
+ .wp-disclosure[data-boxed] .wp-disclosure__head { padding: 0 var(--wp-space-3); }
95
+ .wp-disclosure__trigger {
96
+ display: inline-flex;
97
+ align-items: center;
98
+ gap: var(--wp-space-3);
99
+ flex: 1;
100
+ min-width: 0;
101
+ min-height: var(--wp-control-h-md);
102
+ margin: 0;
103
+ padding: 0 var(--wp-space-2);
104
+ border: 0;
105
+ border-radius: var(--wp-radius-sm);
106
+ background: transparent;
107
+ color: var(--wp-text);
108
+ font: 600 var(--wp-text-sm)/1.3 var(--wp-font-sans);
109
+ text-align: left;
110
+ cursor: pointer;
111
+ }
112
+ .wp-disclosure[data-size="sm"] .wp-disclosure__trigger {
113
+ min-height: var(--wp-control-h-sm);
114
+ font-size: var(--wp-text-xs);
115
+ gap: var(--wp-space-2);
116
+ }
117
+ .wp-disclosure__trigger:hover:not(:disabled) { background: var(--wp-row-hover); }
118
+ .wp-disclosure__trigger:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--wp-border-focus); }
119
+ .wp-disclosure__trigger:disabled { opacity: 0.45; cursor: not-allowed; }
120
+ .wp-disclosure__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
121
+ .wp-disclosure__chev {
122
+ flex: none;
123
+ font-size: 0.75em;
124
+ color: var(--wp-text-muted);
125
+ transition: transform var(--wp-motion-swap) var(--wp-motion-curve-collapse);
126
+ }
127
+ .wp-disclosure[data-open="true"] .wp-disclosure__chev { transform: rotate(90deg); }
128
+ .wp-disclosure__count {
129
+ padding: 1px 6px;
130
+ border-radius: var(--wp-radius-cap, 999px);
131
+ background: var(--wp-bg-3);
132
+ color: var(--wp-text-muted);
133
+ font-size: var(--wp-text-xs);
134
+ font-weight: 500;
135
+ font-variant-numeric: tabular-nums;
136
+ }
137
+ .wp-disclosure__lead { display: inline-flex; align-items: center; flex: none; }
138
+ .wp-disclosure__actions { display: inline-flex; align-items: center; gap: var(--wp-space-2); flex: none; }
139
+ /* Grid rows animate from 0fr to 1fr, so the body grows to its own height
140
+ without measuring it. */
141
+ .wp-disclosure__body {
142
+ display: grid;
143
+ grid-template-rows: 0fr;
144
+ transition: grid-template-rows var(--wp-motion-swap) var(--wp-motion-curve-collapse);
145
+ }
146
+ .wp-disclosure[data-open="true"] > .wp-disclosure__body { grid-template-rows: 1fr; }
147
+ .wp-disclosure__inner { min-height: 0; overflow: hidden; }
148
+ .wp-disclosure__content { padding: var(--wp-space-3) 0 var(--wp-space-2) calc(var(--wp-space-2) + 1.1em + var(--wp-space-3)); }
149
+ .wp-disclosure[data-size="sm"] .wp-disclosure__content { padding: var(--wp-space-2) 0 var(--wp-space-2) calc(var(--wp-space-2) + 1em); }
150
+ .wp-disclosure[data-boxed] .wp-disclosure__content { padding: var(--wp-space-2) var(--wp-space-5) var(--wp-space-5); }
151
+ .wp-disclosure[data-flush] > .wp-disclosure__head {
152
+ border-bottom: 1px solid var(--wp-border);
153
+ background: var(--wp-bg-2);
154
+ }
155
+ .wp-disclosure[data-flush] .wp-disclosure__trigger {
156
+ padding: 0 var(--wp-space-5);
157
+ border-radius: 0;
158
+ font-size: var(--wp-text-xs);
159
+ }
160
+ .wp-disclosure[data-flush] .wp-disclosure__trigger:focus-visible { box-shadow: inset 0 0 0 2px var(--wp-border-focus); }
161
+ .wp-disclosure[data-flush] .wp-disclosure__content,
162
+ .wp-disclosure[data-flush][data-size="sm"] .wp-disclosure__content { padding: 0; }
163
+ @media (prefers-reduced-motion: reduce) {
164
+ .wp-disclosure__chev, .wp-disclosure__body { transition: none; }
165
+ }
166
+ </style>
@@ -1,49 +1,49 @@
1
- <script setup lang="ts">
2
- /**
3
- * The six-dot grip on a row you drag to reorder (a module row, a bundle
4
- * child, an injector binding). It only shows where to grab: the row owns the
5
- * drag (HTML drag-and-drop or pointer events), so the grip is hidden from
6
- * screen readers and the row gives keyboard users their own way to move it.
7
- * For a list that the kit should reorder itself, with keyboard moves, use
8
- * SortableList.
9
- *
10
- * <DragHandle /> the canvas row size
11
- * <DragHandle size="md" /> a manager row
12
- *
13
- * Reads only the shared tokens, so it works on the canvas and on a page.
14
- */
15
- withDefaults(defineProps<{
16
- size?: "sm" | "md" | undefined;
17
- /** Tooltip on hover. */
18
- title?: string | undefined;
19
- }>(), { size: "sm", title: "Drag to reorder" });
20
- </script>
21
-
22
- <template>
23
- <span class="wp-drag-grip" :data-size="size" :title="title" aria-hidden="true">
24
- <svg viewBox="0 0 6 12" fill="currentColor" focusable="false">
25
- <circle cx="1.5" cy="2" r="1" />
26
- <circle cx="4.5" cy="2" r="1" />
27
- <circle cx="1.5" cy="6" r="1" />
28
- <circle cx="4.5" cy="6" r="1" />
29
- <circle cx="1.5" cy="10" r="1" />
30
- <circle cx="4.5" cy="10" r="1" />
31
- </svg>
32
- </span>
33
- </template>
34
-
35
- <style scoped>
36
- .wp-drag-grip {
37
- display: inline-grid;
38
- place-items: center;
39
- flex: none;
40
- width: 12px;
41
- height: 20px;
42
- color: var(--wp-text-dim);
43
- cursor: grab;
44
- }
45
- .wp-drag-grip[data-size="md"] { width: 16px; height: 24px; }
46
- .wp-drag-grip svg { width: 6px; height: 12px; }
47
- .wp-drag-grip[data-size="md"] svg { width: 8px; height: 16px; }
48
- .wp-drag-grip:hover { color: var(--wp-text-muted); }
49
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * The six-dot grip on a row you drag to reorder (a module row, a bundle
4
+ * child, an injector binding). It only shows where to grab: the row owns the
5
+ * drag (HTML drag-and-drop or pointer events), so the grip is hidden from
6
+ * screen readers and the row gives keyboard users their own way to move it.
7
+ * For a list that the kit should reorder itself, with keyboard moves, use
8
+ * SortableList.
9
+ *
10
+ * <DragHandle /> the canvas row size
11
+ * <DragHandle size="md" /> a manager row
12
+ *
13
+ * Reads only the shared tokens, so it works on the canvas and on a page.
14
+ */
15
+ withDefaults(defineProps<{
16
+ size?: "sm" | "md" | undefined;
17
+ /** Tooltip on hover. */
18
+ title?: string | undefined;
19
+ }>(), { size: "sm", title: "Drag to reorder" });
20
+ </script>
21
+
22
+ <template>
23
+ <span class="wp-drag-grip" :data-size="size" :title="title" aria-hidden="true">
24
+ <svg viewBox="0 0 6 12" fill="currentColor" focusable="false">
25
+ <circle cx="1.5" cy="2" r="1" />
26
+ <circle cx="4.5" cy="2" r="1" />
27
+ <circle cx="1.5" cy="6" r="1" />
28
+ <circle cx="4.5" cy="6" r="1" />
29
+ <circle cx="1.5" cy="10" r="1" />
30
+ <circle cx="4.5" cy="10" r="1" />
31
+ </svg>
32
+ </span>
33
+ </template>
34
+
35
+ <style scoped>
36
+ .wp-drag-grip {
37
+ display: inline-grid;
38
+ place-items: center;
39
+ flex: none;
40
+ width: 12px;
41
+ height: 20px;
42
+ color: var(--wp-text-dim);
43
+ cursor: grab;
44
+ }
45
+ .wp-drag-grip[data-size="md"] { width: 16px; height: 24px; }
46
+ .wp-drag-grip svg { width: 6px; height: 12px; }
47
+ .wp-drag-grip[data-size="md"] svg { width: 8px; height: 16px; }
48
+ .wp-drag-grip:hover { color: var(--wp-text-muted); }
49
+ </style>