@dumiflex/wp-kit 0.9.0 → 0.11.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.0](https://github.com/DumiFlex/wp-kit/compare/v0.10.0...v0.11.0) (2026-11-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * Popover takes a virtual anchor and passes attrs to its panel; Input and Textarea style themselves on the canvas ([#94](https://github.com/DumiFlex/wp-kit/issues/94)) ([d5e17ef](https://github.com/DumiFlex/wp-kit/commit/d5e17eff909e259d1ed8dbfa3b0fbc2060e9e213))
9
+
10
+ ## [0.10.0](https://github.com/DumiFlex/wp-kit/compare/v0.9.0...v0.10.0) (2026-11-03)
11
+
12
+
13
+ ### Features
14
+
15
+ * Combobox mono, for variable names and ids ([e9ddc4f](https://github.com/DumiFlex/wp-kit/commit/e9ddc4f7a5c78e1a2f2adfe8d69046f771d95840))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * Chip carries its own base styles, so it is styled on the ComfyUI canvas ([5d81a79](https://github.com/DumiFlex/wp-kit/commit/5d81a791010a9a72c5491dfc08cc1ed44af1b25f))
21
+
3
22
  ## [0.9.0](https://github.com/DumiFlex/wp-kit/compare/v0.8.0...v0.9.0) (2026-11-03)
4
23
 
5
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dumiflex/wp-kit",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Vue 3 component kit shared by the Wildcard Pipeline manager, canvas and community site.",
5
5
  "keywords": [
6
6
  "vue",
package/src/Chip.vue CHANGED
@@ -129,6 +129,47 @@ function onRemove(e: MouseEvent) {
129
129
  </template>
130
130
 
131
131
  <style scoped>
132
+ /* The base pill, its tones, dot and ✕ live here too, not only in the page
133
+ theme (theme/tokens.css), so a Chip on the ComfyUI canvas, which never
134
+ loads that sheet, is styled the same. Shared tokens only. */
135
+ .wp-chip {
136
+ display: inline-flex; align-items: center; gap: 4px;
137
+ padding: 1px 7px;
138
+ border: 1px solid var(--wp-border);
139
+ border-radius: var(--wp-radius-cap, 999px);
140
+ background: var(--wp-bg-3);
141
+ color: var(--wp-text-muted);
142
+ font-family: var(--wp-font-sans);
143
+ font-size: 10.5px;
144
+ font-weight: 500;
145
+ white-space: nowrap;
146
+ }
147
+ .wp-chip--accent {
148
+ color: var(--wp-accent-text-strong);
149
+ background: color-mix(in oklab, var(--wp-accent-500) 16%, transparent);
150
+ border-color: color-mix(in oklab, var(--wp-accent-500) 38%, transparent);
151
+ }
152
+ .wp-chip--success { color: var(--wp-success); background: color-mix(in oklab, var(--wp-success) 14%, transparent); border-color: color-mix(in oklab, var(--wp-success) 36%, transparent); }
153
+ .wp-chip--warn { color: var(--wp-warn); background: color-mix(in oklab, var(--wp-warn) 14%, transparent); border-color: color-mix(in oklab, var(--wp-warn) 36%, transparent); }
154
+ .wp-chip--danger { color: var(--wp-danger); background: color-mix(in oklab, var(--wp-danger) 14%, transparent); border-color: color-mix(in oklab, var(--wp-danger) 36%, transparent); }
155
+ .wp-chip--info { color: var(--wp-info); background: color-mix(in oklab, var(--wp-info) 14%, transparent); border-color: color-mix(in oklab, var(--wp-info) 36%, transparent); }
156
+ .wp-chip__dot { width: 7px; height: 7px; border-radius: var(--wp-radius-cap, 999px); background: currentColor; }
157
+ .wp-chip__close {
158
+ position: relative; display: inline-grid; place-items: center;
159
+ width: 12px; height: 12px; margin: 0 -2px 0 1px; padding: 0;
160
+ border: none; border-radius: 0; background: none;
161
+ color: inherit; opacity: 0.65; cursor: pointer; line-height: 0;
162
+ transition: opacity var(--wp-motion-quick) ease;
163
+ }
164
+ .wp-chip__close::after { content: ""; position: absolute; inset: -4px; }
165
+ .wp-chip__close:hover { opacity: 1; }
166
+ .wp-chip__close:focus-visible { outline: 2px solid var(--wp-accent-500); outline-offset: 1px; border-radius: min(2px, var(--wp-radius-cap, 999px)); opacity: 1; }
167
+ .wp-chip--color { font-weight: 500; }
168
+ /* A bordered pill on the canvas: a radius a hair under half its height, so
169
+ Firefox draws it in one piece at ComfyUI's zooms (0.64, 0.8). */
170
+ .wp-chip--xs { border-radius: min(9px, var(--wp-radius-cap, 999px)); }
171
+ .wp-chip--sm { border-radius: min(calc(var(--wp-control-h-sm) / 2 - 1px), var(--wp-radius-cap, 999px)); }
172
+ .wp-chip--md { border-radius: min(calc(var(--wp-control-h-md) / 2 - 1px), var(--wp-radius-cap, 999px)); }
132
173
  /* xs is the base `.wp-chip` in theme/tokens.css, held at 20px here: its
133
174
  content height was 19.2px, which put every icon half a pixel off. */
134
175
  .wp-chip--xs { box-sizing: border-box; height: 20px; }
@@ -207,7 +248,7 @@ function onRemove(e: MouseEvent) {
207
248
  }
208
249
  .wp-chip__stat-n {
209
250
  font-size: var(--wp-text-xl);
210
- font-weight: var(--wp-weight-semibold);
251
+ font-weight: 600;
211
252
  font-variant-numeric: tabular-nums;
212
253
  line-height: 1.2;
213
254
  }
package/src/Combobox.vue CHANGED
@@ -10,6 +10,8 @@
10
10
  * <Combobox v-model="model" :options="models" aria-label="Model" />
11
11
  * <Combobox v-model="cats" multiple :options="categories" aria-label="Categories" />
12
12
  *
13
+ * `mono` sets the box and the options in the mono font (variable names, ids).
14
+ *
13
15
  * APG "combobox with list autocomplete": focus stays in the text field and
14
16
  * the lit option is named by aria-activedescendant.
15
17
  */
@@ -33,6 +35,8 @@ const props = withDefaults(defineProps<{
33
35
  emptyText?: string | undefined;
34
36
  /** How many options the list shows at once (it scrolls past that). */
35
37
  limit?: number | undefined;
38
+ /** The box and the options in the mono font (variable names, ids). */
39
+ mono?: boolean | undefined;
36
40
  }>(), {
37
41
  multiple: false, placeholder: "Choose…", size: "md", ariaLabel: "Choose",
38
42
  clearable: false, emptyText: "No matches", limit: 50,
@@ -186,6 +190,7 @@ function setOpen(v: boolean): void {
186
190
  :data-multiple="multiple || undefined"
187
191
  :data-disabled="disabled || undefined"
188
192
  :data-invalid="invalid || undefined"
193
+ :data-mono="mono || undefined"
189
194
  @click="inputEl?.focus(); wanted = true"
190
195
  >
191
196
  <template v-if="multiple">
@@ -238,6 +243,7 @@ function setOpen(v: boolean): void {
238
243
  role="listbox"
239
244
  :aria-label="`${ariaLabel} choices`"
240
245
  :aria-multiselectable="multiple || undefined"
246
+ :data-mono="mono || undefined"
241
247
  >
242
248
  <!-- Options are lit with aria-activedescendant; the text field keeps focus and the keys. -->
243
249
  <!-- eslint-disable-next-line vuejs-accessibility/click-events-have-key-events, vuejs-accessibility/interactive-supports-focus -->
@@ -324,4 +330,7 @@ function setOpen(v: boolean): void {
324
330
  @media (prefers-reduced-motion: reduce) {
325
331
  .wp-combo { transition: none; }
326
332
  }
333
+ /* mono: names and ids read in the mono font, in the box and the list. */
334
+ .wp-combo[data-mono] .wp-combo__field, .wp-combo[data-mono] .wp-combo__chip-text,
335
+ .wp-combo__list[data-mono] .wp-combo__opt-text { font-family: var(--wp-font-mono); }
327
336
  </style>
package/src/Input.vue CHANGED
@@ -97,3 +97,5 @@ function onInput(e: Event) {
97
97
  @focus="(e) => emit('focus', e)"
98
98
  />
99
99
  </template>
100
+
101
+ <style scoped src="./input.css"></style>
package/src/Popover.vue CHANGED
@@ -17,6 +17,15 @@
17
17
  *
18
18
  * <Popover :open="!!cell" :anchor="cell?.el" label="Cell rule" @update:open="cell = null">…</Popover>
19
19
  *
20
+ * A list that follows the text caret, or a card opened at a point, passes a
21
+ * virtual anchor instead: anything with `getBoundingClientRect()` (see
22
+ * `VirtualAnchor` in floating.ts). Hand it a new object to move the panel.
23
+ *
24
+ * <Popover :open="!!at" :anchor="at" :autofocus="false" role="listbox" @update:open="at = null">…</Popover>
25
+ *
26
+ * Attributes (`class`, `data-test`, `@mouseenter` …) go on the panel, so a
27
+ * hover card can keep itself open while the pointer is over it.
28
+ *
20
29
  * `variant="menu"` draws the panel as a menu (`menu.css`, the look Select's
21
30
  * list, ContextMenu and Combobox share): tighter padding, 4px from the
22
31
  * trigger, above modals, and it closes once its trigger scrolls out of view.
@@ -24,9 +33,11 @@
24
33
  * scrolls past that instead of covering its trigger.
25
34
  */
26
35
  import { computed, nextTick, onBeforeUnmount, ref, useId, watch } from "vue";
27
- import { placeFloating, roomOn, viewportSize, type Placement, type Side } from "./floating";
36
+ import { placeFloating, roomOn, viewportSize, type Placement, type Side, type VirtualAnchor } from "./floating";
28
37
  import { provideFloatingLayer, useFloatingChild } from "./floating-layer";
29
38
 
39
+ defineOptions({ inheritAttrs: false });
40
+
30
41
  const props = withDefaults(defineProps<{
31
42
  open: boolean;
32
43
  placement?: Placement | undefined;
@@ -38,8 +49,9 @@ const props = withDefaults(defineProps<{
38
49
  autofocus?: boolean | undefined;
39
50
  /** "dialog" for panels with controls, "menu"/"listbox" when the content is one. */
40
51
  role?: "dialog" | "menu" | "listbox" | "group" | undefined;
41
- /** Place the panel against this element instead of the trigger slot. */
42
- anchor?: HTMLElement | null | undefined;
52
+ /** Place the panel against this element (or a virtual anchor: a caret, a
53
+ * point) instead of the trigger slot. */
54
+ anchor?: HTMLElement | VirtualAnchor | null | undefined;
43
55
  /** "menu" for a list of choices (Select, Combobox): the shared menu look. */
44
56
  variant?: "panel" | "menu" | undefined;
45
57
  /** Tallest the panel grows, in px, before it scrolls. */
@@ -59,12 +71,19 @@ const MIN_HEIGHT = 80;
59
71
  const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), '
60
72
  + 'textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
61
73
 
62
- function trigger(): HTMLElement | null {
74
+ /** What the panel is placed against: the anchor prop or the trigger slot. */
75
+ function trigger(): HTMLElement | VirtualAnchor | null {
63
76
  if (props.anchor) return props.anchor;
64
77
  const el = anchorEl.value;
65
78
  return (el?.firstElementChild as HTMLElement | null) ?? el;
66
79
  }
67
80
 
81
+ /** The anchor as an element, when it is one (a virtual anchor takes no focus). */
82
+ function anchorElement(): HTMLElement | null {
83
+ const a = props.anchor;
84
+ return a instanceof HTMLElement ? a : null;
85
+ }
86
+
68
87
  function setOpen(v: boolean): void {
69
88
  if (v !== props.open) emit("update:open", v);
70
89
  }
@@ -105,7 +124,9 @@ function triggerGone(): boolean {
105
124
  if (!t) return false;
106
125
  const r = t.getBoundingClientRect();
107
126
  const view = viewportSize();
108
- return r.bottom < 0 || r.top > view.height || r.right < 0 || r.left > view.width;
127
+ const bottom = r.top + r.height;
128
+ const right = r.left + r.width;
129
+ return bottom < 0 || r.top > view.height || right < 0 || r.left > view.width;
109
130
  }
110
131
 
111
132
  function onScroll(ev: Event): void {
@@ -126,7 +147,8 @@ function onPointerDown(ev: PointerEvent): void {
126
147
  if (layer.owns(path)) return;
127
148
  if (panelEl.value && path.includes(panelEl.value)) return;
128
149
  if (anchorEl.value && path.includes(anchorEl.value)) return;
129
- if (props.anchor && path.includes(props.anchor)) return;
150
+ const own = anchorElement();
151
+ if (own && path.includes(own)) return;
130
152
  setOpen(false);
131
153
  }
132
154
 
@@ -137,7 +159,8 @@ function onKeydown(ev: KeyboardEvent): void {
137
159
  ev.preventDefault();
138
160
  ev.stopPropagation();
139
161
  setOpen(false);
140
- trigger()?.focus();
162
+ const t = trigger();
163
+ if (t instanceof HTMLElement) t.focus();
141
164
  }
142
165
 
143
166
  function listen(on: boolean): void {
@@ -183,6 +206,7 @@ defineExpose({ place });
183
206
  :role="role"
184
207
  :aria-label="label"
185
208
  tabindex="-1"
209
+ v-bind="$attrs"
186
210
  :style="{
187
211
  left: `${pos.x}px`,
188
212
  top: `${pos.y}px`,
package/src/Textarea.vue CHANGED
@@ -55,3 +55,5 @@ watch(() => props.modelValue, () => {
55
55
  @focus="(e) => emit('focus', e)"
56
56
  />
57
57
  </template>
58
+
59
+ <style scoped src="./input.css"></style>
package/src/floating.ts CHANGED
@@ -17,6 +17,18 @@ export interface Rect {
17
17
  height: number;
18
18
  }
19
19
 
20
+ /** Something a panel can be placed against without being an element: the
21
+ * text caret, a selection, a point the pointer was at. */
22
+ export interface VirtualAnchor {
23
+ getBoundingClientRect(): Rect;
24
+ }
25
+
26
+ /** A virtual anchor for a fixed rectangle (a point is width and height 0). */
27
+ export function rectAnchor(rect: Rect): VirtualAnchor {
28
+ const r = { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
29
+ return { getBoundingClientRect: () => r };
30
+ }
31
+
20
32
  export interface Size {
21
33
  width: number;
22
34
  height: number;
package/src/input.css ADDED
@@ -0,0 +1,113 @@
1
+ /* Input and Textarea: the text field and the box around a prefixed one
2
+ * (`.wp-input-group`: an icon, a `$` prefix, a unit or the readonly lock).
3
+ * Scoped into Input.vue and Textarea.vue so a field looks right on the
4
+ * canvas, which never loads theme/tokens.css; the theme imports it too so
5
+ * a page can still write `.wp-input-group` around another field. Shared
6
+ * tokens only. The other field pieces draw field.css' `.wp-field-box`. */
7
+
8
+ .wp-input,
9
+ .wp-textarea {
10
+ height: var(--wp-control-h-md);
11
+ width: 100%;
12
+ background: var(--wp-bg-2);
13
+ color: var(--wp-text);
14
+ border: 1px solid var(--wp-border);
15
+ border-radius: var(--wp-radius-sm);
16
+ padding: 0 10px;
17
+ font-size: 12.5px;
18
+ outline: none;
19
+ transition: border-color var(--wp-motion-quick), background var(--wp-motion-quick), box-shadow var(--wp-motion-quick);
20
+ }
21
+ .wp-textarea {
22
+ height: auto;
23
+ padding: 8px 10px;
24
+ resize: vertical;
25
+ min-height: var(--wp-control-h-md);
26
+ line-height: 1.5;
27
+ }
28
+ .wp-input:focus-visible,
29
+ .wp-textarea:focus-visible {
30
+ border-color: var(--wp-accent-500);
31
+ box-shadow: var(--wp-focus-ring);
32
+ background: var(--wp-bg-1);
33
+ }
34
+ /* Retain :focus fallback for browsers/contexts that don't support :focus-visible */
35
+ .wp-input:focus,
36
+ .wp-textarea:focus {
37
+ border-color: var(--wp-accent-500);
38
+ box-shadow: var(--wp-focus-ring);
39
+ background: var(--wp-bg-1);
40
+ }
41
+ /* Error state on standalone controls (when not wrapped in .wp-field) */
42
+ .wp-input[aria-invalid="true"],
43
+ .wp-textarea[aria-invalid="true"] {
44
+ border-color: var(--wp-error-border);
45
+ }
46
+ .wp-input::placeholder, .wp-textarea::placeholder { color: var(--wp-text-dim); }
47
+ .wp-input--sm { height: var(--wp-control-h-sm); font-size: 12px; padding: 0 8px; }
48
+ .wp-input--lg { height: var(--wp-control-h-lg); font-size: 13.5px; padding: 0 12px; }
49
+ /* Readonly: shown, not editable. Dimmer fill, no focus ring (Input adds a lock). */
50
+ .wp-input[readonly],
51
+ .wp-textarea[readonly] {
52
+ background: var(--wp-bg-1);
53
+ color: var(--wp-text-muted);
54
+ border-style: dashed;
55
+ cursor: default;
56
+ }
57
+ .wp-input[readonly]:focus,
58
+ .wp-input[readonly]:focus-visible,
59
+ .wp-textarea[readonly]:focus,
60
+ .wp-textarea[readonly]:focus-visible { box-shadow: none; border-color: var(--wp-border-strong); background: var(--wp-bg-1); }
61
+ .wp-input:disabled,
62
+ .wp-textarea:disabled { opacity: 0.45; cursor: not-allowed; }
63
+
64
+ .wp-input-group {
65
+ display: inline-flex; align-items: stretch; width: 100%;
66
+ border: 1px solid var(--wp-border); border-radius: var(--wp-radius-sm);
67
+ background: var(--wp-bg-2);
68
+ overflow: hidden;
69
+ height: var(--wp-control-h-md);
70
+ min-height: var(--wp-control-h-md);
71
+ flex-shrink: 0;
72
+ transition: border-color var(--wp-motion-quick), box-shadow var(--wp-motion-quick);
73
+ }
74
+ .wp-input-group:focus-within {
75
+ border-color: var(--wp-accent-500);
76
+ box-shadow: var(--wp-focus-ring);
77
+ }
78
+ /* Inline icon and prefix/suffix text: no box, no divider, sitting in the
79
+ * field's own padding so "$" reads as part of "$hair". */
80
+ .wp-input-group__icon,
81
+ .wp-input-group__affix,
82
+ .wp-input-group__lock {
83
+ display: inline-flex; align-items: center; flex-shrink: 0;
84
+ color: var(--wp-text-dim);
85
+ user-select: none;
86
+ }
87
+ .wp-input-group__icon { padding-left: 10px; }
88
+ .wp-input-group__icon .pi { font-size: 14px; line-height: 1; }
89
+ .wp-input-group__affix { font-family: var(--wp-font-mono); font-size: 12.5px; }
90
+ .wp-input-group__affix--prefix { padding-left: 10px; }
91
+ .wp-input-group__affix--suffix { padding-right: 10px; }
92
+ .wp-input-group__lock { padding-right: 10px; }
93
+ .wp-input-group__icon + .wp-input,
94
+ .wp-input-group__icon + .wp-input-group__affix--prefix { padding-left: 7px; }
95
+ .wp-input-group__affix--prefix + .wp-input { padding-left: 2px; }
96
+ .wp-input-group .wp-input:has(+ .wp-input-group__affix--suffix),
97
+ .wp-input-group .wp-input:has(+ .wp-input-group__lock) { padding-right: 4px; }
98
+ .wp-input-group--sm { height: var(--wp-control-h-sm); min-height: var(--wp-control-h-sm); }
99
+ .wp-input-group--lg { height: var(--wp-control-h-lg); min-height: var(--wp-control-h-lg); }
100
+ .wp-input-group--readonly { background: var(--wp-bg-1); border-style: dashed; }
101
+ .wp-input-group--readonly:focus-within { box-shadow: none; border-color: var(--wp-border-strong); }
102
+ .wp-input-group--readonly .wp-input[readonly] { border: none; }
103
+ .wp-input-group--disabled { opacity: 0.45; cursor: not-allowed; }
104
+ .wp-input-group--disabled .wp-input:disabled { opacity: 1; }
105
+ .wp-input-group[aria-invalid="true"] { border-color: var(--wp-error-border); }
106
+ .wp-input-group .wp-input {
107
+ border: none !important;
108
+ background: transparent !important;
109
+ border-radius: 0;
110
+ height: 100%;
111
+ flex: 1;
112
+ }
113
+ .wp-input-group .wp-input:focus { box-shadow: none; }
@@ -1,3 +1,4 @@
1
+ @import "../input.css";
1
2
  /* =========================================================
2
3
  Wildcard Pipeline — Design Tokens & Base
3
4
  PrimeVue 4 Aura-inspired, dark, brand-customized.
@@ -704,10 +705,11 @@ input, textarea, select { font: inherit; }
704
705
  * canvas Button (component standard, rule 8). main.ts loads it right after
705
706
  * this file. */
706
707
 
707
- /* ---------- Inputs ---------- */
708
- .wp-input,
709
- .wp-select,
710
- .wp-textarea {
708
+ /* ---------- Inputs ----------
709
+ * Input and Textarea (and the .wp-input-group around a prefixed field) are
710
+ * drawn by ../input.css, imported at the top of this file and scoped into
711
+ * Input.vue / Textarea.vue so they look the same on the canvas. */
712
+ .wp-select {
711
713
  height: var(--wp-input-h);
712
714
  width: 100%;
713
715
  background: var(--wp-bg-2);
@@ -719,51 +721,14 @@ input, textarea, select { font: inherit; }
719
721
  outline: none;
720
722
  transition: border-color .12s, background .12s, box-shadow .12s;
721
723
  }
722
- .wp-textarea {
723
- height: auto;
724
- padding: 8px 10px;
725
- resize: vertical;
726
- min-height: var(--wp-input-h);
727
- line-height: 1.5;
728
- }
729
- .wp-input:focus-visible,
730
724
  .wp-select:focus-visible,
731
- .wp-textarea:focus-visible {
725
+ .wp-select:focus {
732
726
  border-color: var(--wp-accent-500);
733
727
  box-shadow: var(--wp-focus-ring);
734
728
  background: var(--wp-bg-1);
735
729
  }
736
- /* Retain :focus fallback for browsers/contexts that don't support :focus-visible */
737
- .wp-input:focus,
738
- .wp-select:focus,
739
- .wp-textarea:focus {
740
- border-color: var(--wp-accent-500);
741
- box-shadow: var(--wp-focus-ring);
742
- background: var(--wp-bg-1);
743
- }
744
- /* Error state on standalone controls (when not wrapped in .wp-field) */
745
- .wp-input[aria-invalid="true"],
746
- .wp-select[aria-invalid="true"],
747
- .wp-textarea[aria-invalid="true"] {
748
- border-color: var(--wp-error-border);
749
- }
750
- .wp-input::placeholder, .wp-textarea::placeholder { color: var(--wp-text-dim); }
751
- .wp-input--sm, .wp-select--sm { height: var(--wp-input-h-sm); font-size: 12px; padding: 0 8px; }
752
- .wp-input--lg { height: var(--wp-control-h-lg); font-size: 13.5px; padding: 0 12px; }
753
- /* Readonly: shown, not editable. Dimmer fill, no focus ring (Input adds a lock). */
754
- .wp-input[readonly],
755
- .wp-textarea[readonly] {
756
- background: var(--wp-bg-1);
757
- color: var(--wp-text-muted);
758
- border-style: dashed;
759
- cursor: default;
760
- }
761
- .wp-input[readonly]:focus,
762
- .wp-input[readonly]:focus-visible,
763
- .wp-textarea[readonly]:focus,
764
- .wp-textarea[readonly]:focus-visible { box-shadow: none; border-color: var(--wp-border-strong); background: var(--wp-bg-1); }
765
- .wp-input:disabled,
766
- .wp-textarea:disabled { opacity: 0.45; cursor: not-allowed; }
730
+ .wp-select[aria-invalid="true"] { border-color: var(--wp-error-border); }
731
+ .wp-select--sm { height: var(--wp-input-h-sm); font-size: 12px; padding: 0 8px; }
767
732
  /* Custom select trigger button — flex layout so label + spacer + chevron align. */
768
733
  .wp-select {
769
734
  display: flex;
@@ -793,56 +758,7 @@ input, textarea, select { font: inherit; }
793
758
  border-color: var(--wp-error-border);
794
759
  }
795
760
 
796
- .wp-input-group {
797
- display: inline-flex; align-items: stretch; width: 100%;
798
- border: 1px solid var(--wp-border); border-radius: var(--wp-radius-sm);
799
- background: var(--wp-bg-2);
800
- overflow: hidden;
801
- height: var(--wp-input-h);
802
- min-height: var(--wp-input-h);
803
- flex-shrink: 0;
804
- transition: border-color .12s, box-shadow .12s;
805
- }
806
- .wp-input-group:focus-within {
807
- border-color: var(--wp-accent-500);
808
- box-shadow: var(--wp-focus-ring);
809
- }
810
- /* Inline icon and prefix/suffix text: no box, no divider, sitting in the
811
- * field's own padding so "$" reads as part of "$hair". */
812
- .wp-input-group__icon,
813
- .wp-input-group__affix,
814
- .wp-input-group__lock {
815
- display: inline-flex; align-items: center; flex-shrink: 0;
816
- color: var(--wp-text-dim);
817
- user-select: none;
818
- }
819
- .wp-input-group__icon { padding-left: 10px; }
820
- .wp-input-group__icon .pi { font-size: 14px; line-height: 1; }
821
- .wp-input-group__affix { font-family: var(--wp-font-mono); font-size: 12.5px; }
822
- .wp-input-group__affix--prefix { padding-left: 10px; }
823
- .wp-input-group__affix--suffix { padding-right: 10px; }
824
- .wp-input-group__lock { padding-right: 10px; }
825
- .wp-input-group__icon + .wp-input,
826
- .wp-input-group__icon + .wp-input-group__affix--prefix { padding-left: 7px; }
827
- .wp-input-group__affix--prefix + .wp-input { padding-left: 2px; }
828
- .wp-input-group .wp-input:has(+ .wp-input-group__affix--suffix),
829
- .wp-input-group .wp-input:has(+ .wp-input-group__lock) { padding-right: 4px; }
830
- .wp-input-group--sm { height: var(--wp-control-h-sm); min-height: var(--wp-control-h-sm); }
831
- .wp-input-group--lg { height: var(--wp-control-h-lg); min-height: var(--wp-control-h-lg); }
832
- .wp-input-group--readonly { background: var(--wp-bg-1); border-style: dashed; }
833
- .wp-input-group--readonly:focus-within { box-shadow: none; border-color: var(--wp-border-strong); }
834
- .wp-input-group--readonly .wp-input[readonly] { border: none; }
835
- .wp-input-group--disabled { opacity: 0.45; cursor: not-allowed; }
836
- .wp-input-group--disabled .wp-input:disabled { opacity: 1; }
837
- .wp-input-group[aria-invalid="true"] { border-color: var(--wp-error-border); }
838
- .wp-input-group .wp-input {
839
- border: none !important;
840
- background: transparent !important;
841
- border-radius: 0;
842
- height: 100%;
843
- flex: 1;
844
- }
845
- .wp-input-group .wp-input:focus { box-shadow: none; }
761
+
846
762
 
847
763
  /* ---------- Chips / tags ---------- */
848
764
  .wp-chip {
package/src/tokens.ts CHANGED
@@ -57,9 +57,9 @@ export const LOCAL_TOKENS = ["--wp-hsv-hue", "--wp-lm-o", "--wp-select-line-h",
57
57
  /** Pieces that need a page with src/theme/ loaded (not the canvas). */
58
58
  export const PAGE_PIECES = [
59
59
  "axis-color.ts", "kind-colour.ts", "matrix-axis.ts", "BundleMeta.vue", "ConstraintMatrixPreview.vue", "PostCard.vue", "TokenizedValue.vue",
60
- "Card.vue", "Chip.vue", "ChoiceCards.vue",
61
- "DataTable.vue", "Drawer.vue", "EmptyState.vue", "Field.vue", "Icon.vue", "Input.vue",
62
- "ListFilter.vue", "RelativeDate.vue", "Select.vue", "Textarea.vue", "ToastHost.vue",
60
+ "Card.vue", "ChoiceCards.vue",
61
+ "DataTable.vue", "Drawer.vue", "EmptyState.vue", "Field.vue", "Icon.vue",
62
+ "ListFilter.vue", "RelativeDate.vue", "Select.vue", "ToastHost.vue",
63
63
  // The payload previews and post loading shapes (community post pages).
64
64
  "PayloadPreview.vue", "WildcardPreview.vue", "FixedValuesPreview.vue", "CombinePreview.vue", "DerivationPreview.vue",
65
65
  "DerivationCondition.vue", "ConstraintPreview.vue", "BundlePreview.vue", "PreviewGroup.vue", "preview.css", "RichText.vue",