@dumiflex/wp-kit 0.5.0 → 0.7.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 (89) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +5 -0
  3. package/package.json +4 -3
  4. package/src/Avatar.vue +1 -1
  5. package/src/Banner.vue +4 -4
  6. package/src/BundlePreview.vue +254 -0
  7. package/src/Button.vue +73 -44
  8. package/src/Checkbox.vue +201 -63
  9. package/src/Chip.vue +5 -2
  10. package/src/CodeBlock.vue +1 -1
  11. package/src/ColorField.vue +60 -6
  12. package/src/CombinePreview.vue +150 -0
  13. package/src/Combobox.vue +10 -14
  14. package/src/ConfirmModal.vue +12 -22
  15. package/src/ConstraintMatrixPreview.vue +6 -22
  16. package/src/ConstraintPreview.vue +259 -0
  17. package/src/ContextMenu.vue +11 -30
  18. package/src/CopyButton.vue +1 -1
  19. package/src/DataTable.vue +0 -2
  20. package/src/DatePicker.vue +1 -1
  21. package/src/DerivationCondition.vue +68 -0
  22. package/src/DerivationPreview.vue +345 -0
  23. package/src/Disclosure.vue +2 -1
  24. package/src/Drawer.vue +2 -2
  25. package/src/DropZone.vue +2 -2
  26. package/src/FixedValuesPreview.vue +184 -0
  27. package/src/HsvPicker.vue +2 -2
  28. package/src/IconPicker.vue +32 -103
  29. package/src/IconPickerPanel.vue +132 -0
  30. package/src/ImageGrid.vue +2 -2
  31. package/src/Lightbox.vue +2 -2
  32. package/src/LoadingMark.vue +6 -61
  33. package/src/Modal.vue +109 -44
  34. package/src/ModalShell.vue +26 -5
  35. package/src/Pagination.vue +4 -4
  36. package/src/PayloadPreview.vue +154 -0
  37. package/src/Popover.vue +68 -12
  38. package/src/PostDetailSkeleton.vue +65 -0
  39. package/src/PostGridSkeleton.vue +61 -0
  40. package/src/PreviewGroup.vue +75 -0
  41. package/src/ProfileSkeleton.vue +73 -0
  42. package/src/RadioGroup.vue +5 -4
  43. package/src/RichText.vue +61 -0
  44. package/src/SearchInput.vue +1 -1
  45. package/src/Select.vue +53 -158
  46. package/src/SelectionBar.vue +90 -0
  47. package/src/SettingGroup.vue +57 -0
  48. package/src/SettingRow.vue +127 -0
  49. package/src/Skeleton.vue +1 -1
  50. package/src/Slider.vue +166 -55
  51. package/src/SortableList.vue +1 -1
  52. package/src/TagInput.vue +1 -1
  53. package/src/Timeline.vue +2 -2
  54. package/src/WildcardPreview.vue +607 -0
  55. package/src/WorkingLine.vue +234 -0
  56. package/src/WpIconButton.vue +3 -2
  57. package/src/WpImageZoom.vue +7 -7
  58. package/src/WpSegmented.vue +3 -1
  59. package/src/WpSwitch.vue +1 -1
  60. package/src/WpTabs.vue +1 -1
  61. package/src/_modal-motion.css +1 -1
  62. package/src/button.css +8 -6
  63. package/src/canvas-motion.ts +251 -0
  64. package/src/floating.ts +5 -0
  65. package/src/hsv.ts +1 -1
  66. package/src/kind-colour.ts +24 -1
  67. package/src/linkify.ts +142 -0
  68. package/src/loading-mark-motion.ts +12 -155
  69. package/src/matrix-axis.ts +28 -0
  70. package/src/menu.css +51 -0
  71. package/src/motion.css +73 -0
  72. package/src/pi-icons.ts +51 -0
  73. package/src/preview-readers.ts +401 -0
  74. package/src/preview.css +194 -0
  75. package/src/slider-math.ts +45 -1
  76. package/src/theme/looks.css +6 -19
  77. package/src/theme/tokens.css +12 -1108
  78. package/src/tokens.ts +15 -4
  79. package/src/tooltip.css +8 -5
  80. package/src/useDialogFocus.ts +11 -2
  81. package/src/working-line-motion.ts +135 -0
  82. package/src/CloseButton.vue +0 -69
  83. package/src/ColorPicker.vue +0 -201
  84. package/src/ConfirmDialog.vue +0 -165
  85. package/src/RangeSlider.vue +0 -183
  86. package/src/Toggle.vue +0 -36
  87. package/src/WpButton.vue +0 -75
  88. package/src/WpCheck.vue +0 -104
  89. package/src/_skeleton.css +0 -50
@@ -0,0 +1,345 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Derivation as rule cards: what it reads / writes / sends to negatives,
4
+ * then per rule IF / ELSE IF / ELSE with nested AND / OR groups and every
5
+ * action numbered (the action plus its schema v8 `extra_actions`).
6
+ */
7
+ import { computed, ref } from "vue";
8
+ import TokenizedValue from "./TokenizedValue.vue";
9
+ import DerivationCondition from "./DerivationCondition.vue";
10
+ import Disclosure from "./Disclosure.vue";
11
+ import {
12
+ derivationRules,
13
+ varFlow,
14
+ type ActionOp,
15
+ type DerivationRule,
16
+ type Obj,
17
+ } from "./preview-readers";
18
+
19
+ const props = defineProps<{
20
+ /** The whole module row (the var summary reads it). */
21
+ payload: Obj | null;
22
+ inner: Obj | null;
23
+ refMap: ReadonlyMap<string, string>;
24
+ }>();
25
+
26
+ const OPEN_RULES = 3;
27
+
28
+ const rules = computed(() => derivationRules(props.inner));
29
+ const flow = computed(() => varFlow(props.payload));
30
+ const branchCount = computed(() =>
31
+ rules.value.reduce((n, r) => n + r.branches.length + (r.fallback ? 1 : 0), 0),
32
+ );
33
+
34
+ const OP_LABEL: Record<ActionOp, string> = {
35
+ set: "SET",
36
+ append: "APPEND",
37
+ prepend: "PREPEND",
38
+ neg_add: "+ NEGATIVE",
39
+ neg_replace: "REPLACE NEG",
40
+ other: "",
41
+ };
42
+ function opLabel(op: ActionOp, mode: string): string {
43
+ return OP_LABEL[op] || mode.toUpperCase();
44
+ }
45
+ function ruleSummary(r: DerivationRule): string {
46
+ const n = r.branches.length;
47
+ const parts = [`${n} branch${n === 1 ? "" : "es"}${r.fallback ? " + else" : ""}`];
48
+ const targets = [
49
+ ...new Set(
50
+ [...r.branches.flatMap((b) => b.actions), ...(r.fallback ?? [])]
51
+ .filter((a) => a.op !== "neg_add" && a.op !== "neg_replace")
52
+ .map((a) => a.target),
53
+ ),
54
+ ];
55
+ if (targets.length) parts.push(`sets ${targets.join(", ")}`);
56
+ return parts.join(" · ");
57
+ }
58
+ const toggled = ref(new Set<number>());
59
+ function isOpen(i: number): boolean {
60
+ const byDefault = i < OPEN_RULES;
61
+ return toggled.value.has(i) ? !byDefault : byDefault;
62
+ }
63
+ function toggle(i: number): void {
64
+ const next = new Set(toggled.value);
65
+ if (next.has(i)) next.delete(i);
66
+ else next.add(i);
67
+ toggled.value = next;
68
+ }
69
+ </script>
70
+
71
+ <template>
72
+ <div class="wp-pv wp-derivation">
73
+ <div class="wp-pv-stats">
74
+ <div class="wp-pv-stat">
75
+ <b>{{ rules.length }}</b><span>rule{{ rules.length === 1 ? "" : "s" }}</span>
76
+ </div>
77
+ <div class="wp-pv-stat">
78
+ <b>{{ branchCount }}</b><span>branches</span>
79
+ </div>
80
+ </div>
81
+ <div class="wp-dv-io">
82
+ <div v-if="flow.reads.length">
83
+ <span class="wp-pv-label">Reads</span>
84
+ <code
85
+ v-for="v in flow.reads"
86
+ :key="`r-${v}`"
87
+ class="wp-dv-var"
88
+ >${{ v }}</code>
89
+ </div>
90
+ <div v-if="flow.sets.length">
91
+ <span class="wp-pv-label">Writes</span>
92
+ <code
93
+ v-for="v in flow.sets"
94
+ :key="`w-${v}`"
95
+ class="wp-dv-var"
96
+ >${{ v }}</code>
97
+ </div>
98
+ <div v-if="flow.negatives.length">
99
+ <span class="wp-pv-label">Negatives</span>
100
+ <code
101
+ v-for="v in flow.negatives"
102
+ :key="`n-${v}`"
103
+ class="wp-dv-var wp-dv-var--neg"
104
+ >{{ v }}</code>
105
+ </div>
106
+ </div>
107
+
108
+ <div
109
+ v-for="(r, ri) in rules"
110
+ :key="ri"
111
+ class="wp-dv-rule wp-derivation__rule"
112
+ >
113
+ <Disclosure
114
+ flush
115
+ lazy
116
+ class="wp-dv-rule__fold"
117
+ :open="isOpen(ri)"
118
+ @update:open="toggle(ri)"
119
+ >
120
+ <template #summary>
121
+ <b class="wp-dv-rule__name">Rule {{ ri + 1 }}</b><span class="wp-dv-rule__sum">{{ ruleSummary(r) }}</span>
122
+ </template>
123
+ <div
124
+ v-for="(b, bi) in r.branches"
125
+ :key="bi"
126
+ class="wp-dv-branch wp-derivation__branch"
127
+ >
128
+ <span class="wp-dv-kw">{{ bi === 0 ? "IF" : "ELSE IF" }}</span>
129
+ <div class="wp-dv-body">
130
+ <DerivationCondition
131
+ v-if="b.condition"
132
+ :cond="b.condition"
133
+ />
134
+ <span
135
+ v-else
136
+ class="wp-dv-none"
137
+ >(no condition)</span>
138
+ <ol class="wp-dv-steps">
139
+ <li
140
+ v-for="(a, ai) in b.actions"
141
+ :key="ai"
142
+ data-test="derivation-action"
143
+ >
144
+ <span class="wp-dv-i">{{ ai + 1 }}</span>
145
+ <span class="wp-dv-op-cell">
146
+ <span
147
+ class="wp-dv-op"
148
+ :data-op="a.op"
149
+ >{{ opLabel(a.op, a.mode) }}</span>
150
+ <code class="wp-dv-target wp-derivation__target">{{ a.target }}</code>
151
+ </span>
152
+ <span class="wp-dv-val"><TokenizedValue
153
+ :value="a.value"
154
+ :uuid-to-name="refMap"
155
+ /></span>
156
+ </li>
157
+ </ol>
158
+ </div>
159
+ </div>
160
+ <div
161
+ v-if="r.fallback"
162
+ class="wp-dv-branch wp-derivation__branch is-else"
163
+ >
164
+ <span class="wp-dv-kw wp-dv-kw--else">ELSE</span>
165
+ <div class="wp-dv-body">
166
+ <ol class="wp-dv-steps">
167
+ <li
168
+ v-for="(a, ai) in r.fallback"
169
+ :key="ai"
170
+ data-test="derivation-action"
171
+ >
172
+ <span class="wp-dv-i">{{ ai + 1 }}</span>
173
+ <span class="wp-dv-op-cell">
174
+ <span
175
+ class="wp-dv-op"
176
+ :data-op="a.op"
177
+ >{{ opLabel(a.op, a.mode) }}</span>
178
+ <code class="wp-dv-target wp-derivation__target">{{ a.target }}</code>
179
+ </span>
180
+ <span class="wp-dv-val"><TokenizedValue
181
+ :value="a.value"
182
+ :uuid-to-name="refMap"
183
+ /></span>
184
+ </li>
185
+ </ol>
186
+ </div>
187
+ </div>
188
+ </Disclosure>
189
+ </div>
190
+ <div class="wp-dv-pad" />
191
+ </div>
192
+ </template>
193
+
194
+ <style scoped src="./preview.css"></style>
195
+ <style scoped>
196
+ .wp-dv-io {
197
+ display: flex;
198
+ flex-wrap: wrap;
199
+ gap: 10px 24px;
200
+ padding: 12px 16px;
201
+ border-bottom: 1px solid var(--wp-border);
202
+ }
203
+ .wp-dv-io > div {
204
+ display: flex;
205
+ flex-wrap: wrap;
206
+ align-items: center;
207
+ gap: 6px;
208
+ }
209
+ .wp-dv-var {
210
+ font-family: var(--wp-font-mono);
211
+ font-size: 12px;
212
+ color: var(--wp-success);
213
+ }
214
+ .wp-dv-var--neg {
215
+ color: var(--wp-danger-text, var(--wp-danger));
216
+ background: color-mix(in oklab, var(--wp-danger) 10%, transparent);
217
+ border-radius: min(3px, var(--wp-radius-cap, 999px));
218
+ padding: 0 4px;
219
+ }
220
+ .wp-dv-rule {
221
+ margin: 14px 16px 0;
222
+ border: 1px solid var(--wp-border);
223
+ border-radius: min(10px, var(--wp-radius-cap, 999px));
224
+ overflow: hidden;
225
+ }
226
+ .wp-dv-rule__name {
227
+ font-size: 12.5px;
228
+ }
229
+ .wp-dv-rule__sum {
230
+ min-width: 0;
231
+ overflow: hidden;
232
+ color: var(--wp-text-dim);
233
+ font-size: 11.5px;
234
+ font-weight: 400;
235
+ text-overflow: ellipsis;
236
+ white-space: nowrap;
237
+ }
238
+ .wp-dv-branch {
239
+ display: grid;
240
+ grid-template-columns: 64px minmax(0, 1fr);
241
+ }
242
+ .wp-dv-branch + .wp-dv-branch {
243
+ border-top: 1px solid var(--wp-border);
244
+ }
245
+ .wp-dv-kw {
246
+ padding: 12px 0 0 14px;
247
+ font-family: var(--wp-font-mono);
248
+ font-size: 10.5px;
249
+ font-weight: 700;
250
+ letter-spacing: 0.06em;
251
+ color: var(--wp-accent-text);
252
+ }
253
+ .wp-dv-kw--else {
254
+ color: var(--wp-text-dim);
255
+ }
256
+ .wp-dv-body {
257
+ display: flex;
258
+ flex-direction: column;
259
+ align-items: flex-start;
260
+ gap: 10px;
261
+ padding: 10px 14px 12px 0;
262
+ min-width: 0;
263
+ }
264
+ .wp-dv-none {
265
+ font-size: 12px;
266
+ color: var(--wp-text-dim);
267
+ }
268
+ .wp-dv-steps {
269
+ list-style: none;
270
+ margin: 0;
271
+ padding: 0;
272
+ width: 100%;
273
+ display: flex;
274
+ flex-direction: column;
275
+ gap: 6px;
276
+ }
277
+ .wp-dv-steps li {
278
+ display: grid;
279
+ grid-template-columns: 18px minmax(120px, max-content) minmax(0, 1fr);
280
+ align-items: baseline;
281
+ gap: 8px;
282
+ }
283
+ .wp-dv-i {
284
+ font-family: var(--wp-font-mono);
285
+ font-size: 11px;
286
+ color: var(--wp-text-dim);
287
+ text-align: right;
288
+ }
289
+ .wp-dv-op-cell {
290
+ display: inline-flex;
291
+ align-items: baseline;
292
+ gap: 6px;
293
+ white-space: nowrap;
294
+ }
295
+ .wp-dv-op {
296
+ font-family: var(--wp-font-mono);
297
+ font-size: 10px;
298
+ font-weight: 600;
299
+ padding: 2px 6px;
300
+ border-radius: min(5px, var(--wp-radius-cap, 999px));
301
+ background: var(--wp-bg-3);
302
+ color: var(--wp-text-muted);
303
+ }
304
+ .wp-dv-op[data-op="set"] {
305
+ background: color-mix(in oklab, var(--wp-success) 14%, transparent);
306
+ color: var(--wp-success);
307
+ }
308
+ .wp-dv-op[data-op="append"],
309
+ .wp-dv-op[data-op="prepend"] {
310
+ background: color-mix(in oklab, var(--wp-info) 16%, transparent);
311
+ color: color-mix(in oklab, var(--wp-info) 55%, white);
312
+ }
313
+ .wp-dv-op[data-op="neg_add"],
314
+ .wp-dv-op[data-op="neg_replace"] {
315
+ background: color-mix(in oklab, var(--wp-danger) 16%, transparent);
316
+ color: var(--wp-danger-text, var(--wp-danger));
317
+ }
318
+ .wp-dv-target {
319
+ font-family: var(--wp-font-mono);
320
+ font-size: 12px;
321
+ color: var(--wp-success);
322
+ }
323
+ .wp-dv-val {
324
+ font-family: var(--wp-font-mono);
325
+ font-size: 12px;
326
+ line-height: 1.6;
327
+ padding: 3px 9px;
328
+ border-radius: min(5px, var(--wp-radius-cap, 999px));
329
+ background: var(--wp-bg-3);
330
+ color: var(--wp-text);
331
+ overflow-wrap: anywhere;
332
+ min-width: 0;
333
+ }
334
+ .wp-dv-pad {
335
+ height: 14px;
336
+ }
337
+ @container (max-width: 640px) {
338
+ .wp-dv-steps li {
339
+ grid-template-columns: 18px minmax(0, 1fr);
340
+ }
341
+ .wp-dv-val {
342
+ grid-column: 2;
343
+ }
344
+ }
345
+ </style>
@@ -120,7 +120,8 @@ function toggle(): void {
120
120
  .wp-disclosure__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
121
121
  .wp-disclosure__chev {
122
122
  flex: none;
123
- font-size: 0.75em;
123
+ /* A whole even size: 0.75em came to 9px and sat half a pixel off. */
124
+ font-size: 10px;
124
125
  color: var(--wp-text-muted);
125
126
  transition: transform var(--wp-motion-swap) var(--wp-motion-curve-collapse);
126
127
  }
package/src/Drawer.vue CHANGED
@@ -5,7 +5,7 @@
5
5
  * Tab stays inside it and focus goes back where it was. With `modal` off
6
6
  * there is no backdrop, Tab moves on to the page and the page stays usable.
7
7
  */
8
- import CloseButton from "./CloseButton.vue";
8
+ import Button from "./Button.vue";
9
9
  import { ref, useId, useSlots } from "vue";
10
10
  import { useDialogFocus } from "./useDialogFocus";
11
11
 
@@ -66,7 +66,7 @@ useDialogFocus(panel, () => props.open, {
66
66
  <h3 v-if="title" :id="titleId" class="wp-drawer__title">{{ title }}</h3>
67
67
  <slot name="header" />
68
68
  <span class="wp-spacer" />
69
- <CloseButton kbd="Esc" data-test="drawer-close" @click="close" />
69
+ <Button variant="ghost" icon="times" aria-label="Close" kbd="Esc" data-test="drawer-close" @click="close" />
70
70
  </header>
71
71
  <div class="wp-drawer__body">
72
72
  <slot />
package/src/DropZone.vue CHANGED
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { computed, ref } from "vue";
17
17
  import { dragItemMaybeAccepted, fileAccepted, formatBytes } from "./drop-zone";
18
- import WpButton from "./WpButton.vue";
18
+ import Button from "./Button.vue";
19
19
 
20
20
  export interface RejectedFile { file: File; reason: string }
21
21
 
@@ -152,7 +152,7 @@ defineExpose({ browse });
152
152
  <span class="wp-drop__label">{{ over === "refuse" ? refuseWhy : mainLabel }}</span>
153
153
  <span v-if="hint && over !== 'refuse'" class="wp-drop__hint">{{ hint }}{{ maxSize ? ` · up to ${formatBytes(maxSize)}` : "" }}</span>
154
154
  </span>
155
- <WpButton class="wp-drop__browse" variant="secondary" size="sm" icon="pi-folder-open" :disabled="disabled" @click="browse">{{ browseLabel }}</WpButton>
155
+ <Button class="wp-drop__browse" variant="secondary" size="sm" icon="pi-folder-open" :disabled="disabled" @click="browse">{{ browseLabel }}</Button>
156
156
  <input ref="inputEl" class="wp-drop__input" type="file" :accept="accept" :multiple="multiple" tabindex="-1" aria-hidden="true" @change="onPicked">
157
157
  </div>
158
158
  </template>
@@ -0,0 +1,184 @@
1
+ <script setup lang="ts">
2
+ /** Fixed values as a searchable variable → value → negative table, paged so
3
+ * a long list stays short until asked. */
4
+ import { computed, ref } from "vue";
5
+ import TokenizedValue from "./TokenizedValue.vue";
6
+ import { fixedValues, scanText, type FixedValue, type Obj } from "./preview-readers";
7
+ import SearchInput from "./SearchInput.vue";
8
+ import Button from "./Button.vue";
9
+ import Chip from "./Chip.vue";
10
+
11
+ const props = defineProps<{
12
+ inner: Obj | null;
13
+ refMap: ReadonlyMap<string, string>;
14
+ }>();
15
+
16
+ const PAGE = 10;
17
+ const LONG_CHARS = 110;
18
+
19
+ const rows = computed(() => fixedValues(props.inner));
20
+ const negCount = computed(() => rows.value.filter((r) => r.negative).length);
21
+ const refCount = computed(
22
+ () =>
23
+ rows.value.filter((r) => {
24
+ const s = scanText(r.value);
25
+ return s.refs || s.picks;
26
+ }).length,
27
+ );
28
+
29
+ const search = ref("");
30
+ const onlyNeg = ref(false);
31
+ const filtering = computed(() => search.value.trim() !== "" || onlyNeg.value);
32
+ const visible = computed<FixedValue[]>(() => {
33
+ const q = search.value.trim().toLowerCase();
34
+ return rows.value.filter(
35
+ (r) =>
36
+ (!onlyNeg.value || r.negative) &&
37
+ (!q ||
38
+ r.name.toLowerCase().includes(q) ||
39
+ r.value.toLowerCase().includes(q) ||
40
+ r.negative.toLowerCase().includes(q)),
41
+ );
42
+ });
43
+ const showAll = ref(false);
44
+ const shown = computed(() =>
45
+ showAll.value || filtering.value ? visible.value : visible.value.slice(0, PAGE),
46
+ );
47
+ const expanded = ref(new Set<number>());
48
+ function toggle(i: number): void {
49
+ const next = new Set(expanded.value);
50
+ if (next.has(i)) next.delete(i);
51
+ else next.add(i);
52
+ expanded.value = next;
53
+ }
54
+ </script>
55
+
56
+ <template>
57
+ <div class="wp-pv">
58
+ <div class="wp-pv-stats">
59
+ <div class="wp-pv-stat">
60
+ <b>{{ rows.length }}</b><span>variable{{ rows.length === 1 ? "" : "s" }}</span>
61
+ </div>
62
+ <div
63
+ v-if="negCount"
64
+ class="wp-pv-stat"
65
+ >
66
+ <b>{{ negCount }}</b><span>with a negative</span>
67
+ </div>
68
+ <div
69
+ v-if="refCount"
70
+ class="wp-pv-stat"
71
+ >
72
+ <b>{{ refCount }}</b><span>use picks / @refs</span>
73
+ </div>
74
+ </div>
75
+ <div
76
+ v-if="rows.length > 6 || negCount"
77
+ class="wp-pv-tool"
78
+ >
79
+ <SearchInput
80
+ v-if="rows.length > 6"
81
+ v-model="search"
82
+ :placeholder="`Filter ${rows.length} values…`"
83
+ data-test="fixed-search"
84
+ class="wp-pv-search"
85
+ />
86
+ <Chip
87
+ v-if="negCount"
88
+ size="sm"
89
+ color="var(--wp-danger)"
90
+ :pressed="onlyNeg"
91
+ @click="onlyNeg = !onlyNeg"
92
+ >Has negative</Chip>
93
+ <span
94
+ v-if="filtering"
95
+ class="wp-pv-count"
96
+ >{{ visible.length }} of {{ rows.length }}</span>
97
+ </div>
98
+ <table class="wp-pv-table">
99
+ <colgroup>
100
+ <col class="wp-pv-col-n">
101
+ <col style="width: 150px">
102
+ <col>
103
+ <col
104
+ v-if="negCount"
105
+ style="width: 28%"
106
+ >
107
+ </colgroup>
108
+ <thead>
109
+ <tr>
110
+ <th />
111
+ <th>Variable</th>
112
+ <th>Value</th>
113
+ <th v-if="negCount">
114
+ Negative
115
+ </th>
116
+ </tr>
117
+ </thead>
118
+ <tbody>
119
+ <tr
120
+ v-for="(r, i) in shown"
121
+ :key="i"
122
+ data-test="fixed-row"
123
+ >
124
+ <td class="wp-pv-n">
125
+ {{ i + 1 }}
126
+ </td>
127
+ <td class="wp-pv-var">
128
+ ${{ r.name }}
129
+ </td>
130
+ <td>
131
+ <div :class="{ 'wp-pv-clamp': r.value.length > LONG_CHARS && !expanded.has(i) }">
132
+ <TokenizedValue
133
+ :value="r.value"
134
+ :uuid-to-name="refMap"
135
+ />
136
+ </div>
137
+ <Button
138
+ v-if="r.value.length > LONG_CHARS"
139
+ variant="link"
140
+ size="sm"
141
+ @click="toggle(i)"
142
+ >{{ expanded.has(i) ? "Show less" : "Show all" }}</Button>
143
+ </td>
144
+ <td
145
+ v-if="negCount"
146
+ class="wp-pv-neg-cell"
147
+ >
148
+ <span
149
+ v-if="r.negative"
150
+ data-test="fixed-negative"
151
+ >
152
+ <span class="wp-pv-negtag">NEG</span>
153
+ <TokenizedValue
154
+ :value="r.negative"
155
+ :uuid-to-name="refMap"
156
+ />
157
+ </span>
158
+ </td>
159
+ </tr>
160
+ </tbody>
161
+ </table>
162
+ <p
163
+ v-if="!visible.length"
164
+ class="wp-pv-empty"
165
+ >
166
+ No values match.
167
+ </p>
168
+ <div
169
+ v-if="!filtering && visible.length > PAGE"
170
+ class="wp-pv-more"
171
+ >
172
+ <Button
173
+ variant="link"
174
+ size="sm"
175
+ data-test="fixed-more"
176
+ @click="showAll = !showAll"
177
+ >
178
+ {{ showAll ? "Show fewer" : `Show ${visible.length - PAGE} more` }}
179
+ </Button>
180
+ </div>
181
+ </div>
182
+ </template>
183
+
184
+ <style scoped src="./preview.css"></style>
package/src/HsvPicker.vue CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * HsvPicker — saturation/value square + hue slider.
4
4
  *
5
- * Shared between manager ColorPicker / TweaksPanel AND the canvas
5
+ * Shared between ColorField / TweaksPanel AND the canvas
6
6
  * bundle modal. CSS uses `--wp-*` tokens with hardcoded fallbacks so
7
7
  * the picker renders inside the extension where the manager spacing
8
8
  * tokens (`--wp-space-*`) aren't loaded.
@@ -167,7 +167,7 @@ const hueCursorStyle = computed(() => ({
167
167
  position: absolute;
168
168
  width: 14px;
169
169
  height: 14px;
170
- border-radius: 50%; /* round: picker cursor on the colour field */
170
+ border-radius: min(50%, var(--wp-radius-cap, 999px));
171
171
  border: 2px solid #fff;
172
172
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 1px 2px rgba(0, 0, 0, 0.45);
173
173
  transform: translate(-50%, -50%);