@dumiflex/wp-kit 0.0.0-stage → 0.3.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 (128) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/LICENSE +674 -0
  3. package/README.md +76 -3
  4. package/package.json +77 -6
  5. package/src/Avatar.vue +83 -0
  6. package/src/Badge.vue +97 -0
  7. package/src/Banner.vue +118 -0
  8. package/src/BookmarkButton.vue +27 -0
  9. package/src/BundleMeta.vue +51 -0
  10. package/src/Button.vue +103 -0
  11. package/src/Card.vue +68 -0
  12. package/src/Checkbox.vue +107 -0
  13. package/src/Chip.vue +97 -0
  14. package/src/ChipToggle.vue +92 -0
  15. package/src/ChoiceCards.vue +133 -0
  16. package/src/CloseButton.vue +69 -0
  17. package/src/CodeBlock.vue +119 -0
  18. package/src/ColorField.vue +206 -0
  19. package/src/ColorPicker.vue +201 -0
  20. package/src/Combobox.vue +334 -0
  21. package/src/ConfirmDialog.vue +163 -0
  22. package/src/ConfirmModal.vue +147 -0
  23. package/src/ConstraintMatrixPreview.vue +708 -0
  24. package/src/ContextMenu.vue +379 -0
  25. package/src/CopyButton.vue +156 -0
  26. package/src/DataTable.vue +292 -0
  27. package/src/DatePicker.vue +234 -0
  28. package/src/DescriptionList.vue +73 -0
  29. package/src/DiffView.vue +68 -0
  30. package/src/Disclosure.vue +166 -0
  31. package/src/DragHandle.vue +49 -0
  32. package/src/Drawer.vue +131 -0
  33. package/src/DropZone.vue +196 -0
  34. package/src/DropdownMenu.vue +72 -0
  35. package/src/EmptyState.vue +111 -0
  36. package/src/Field.vue +28 -0
  37. package/src/HsvPicker.vue +208 -0
  38. package/src/Icon.vue +39 -0
  39. package/src/IconPicker.vue +209 -0
  40. package/src/ImageGrid.vue +125 -0
  41. package/src/InlineEdit.vue +183 -0
  42. package/src/Input.vue +99 -0
  43. package/src/Kbd.vue +56 -0
  44. package/src/KindChip.vue +53 -0
  45. package/src/Lightbox.vue +173 -0
  46. package/src/ListFilter.vue +85 -0
  47. package/src/ListRow.vue +204 -0
  48. package/src/LoadingMark.vue +161 -0
  49. package/src/Logo.vue +60 -0
  50. package/src/Modal.vue +76 -0
  51. package/src/ModalShell.vue +107 -0
  52. package/src/Pagination.vue +106 -0
  53. package/src/Popover.vue +171 -0
  54. package/src/PostCard.vue +211 -0
  55. package/src/ProgressBar.vue +90 -0
  56. package/src/RadioGroup.vue +177 -0
  57. package/src/RangeSlider.vue +183 -0
  58. package/src/RatingBadge.vue +42 -0
  59. package/src/RelativeDate.vue +35 -0
  60. package/src/ResizeHandle.vue +143 -0
  61. package/src/SearchInput.vue +199 -0
  62. package/src/Select.vue +641 -0
  63. package/src/Separator.vue +54 -0
  64. package/src/Skeleton.vue +82 -0
  65. package/src/Slider.vue +198 -0
  66. package/src/SortableList.vue +260 -0
  67. package/src/SplitPane.vue +68 -0
  68. package/src/StarButton.vue +40 -0
  69. package/src/Stepper.vue +141 -0
  70. package/src/SupportLinks.vue +93 -0
  71. package/src/TagInput.vue +300 -0
  72. package/src/Textarea.vue +57 -0
  73. package/src/Timeline.vue +92 -0
  74. package/src/ToastCard.vue +119 -0
  75. package/src/ToastHost.vue +40 -0
  76. package/src/Toggle.vue +36 -0
  77. package/src/TokenizedValue.vue +310 -0
  78. package/src/Toolbar.vue +110 -0
  79. package/src/VirtualList.vue +87 -0
  80. package/src/VisuallyHidden.vue +29 -0
  81. package/src/WpButton.vue +75 -0
  82. package/src/WpCheck.vue +102 -0
  83. package/src/WpIconButton.vue +107 -0
  84. package/src/WpImageZoom.vue +544 -0
  85. package/src/WpNumber.vue +183 -0
  86. package/src/WpSegmented.vue +175 -0
  87. package/src/WpSelect.vue +89 -0
  88. package/src/WpSwitch.vue +163 -0
  89. package/src/WpTabs.vue +188 -0
  90. package/src/_modal-motion.css +33 -0
  91. package/src/_skeleton.css +50 -0
  92. package/src/axis-color.ts +42 -0
  93. package/src/button.css +169 -0
  94. package/src/calendar.ts +59 -0
  95. package/src/clipboard.ts +50 -0
  96. package/src/colour-presets.ts +12 -0
  97. package/src/diff.ts +51 -0
  98. package/src/drop-zone.ts +41 -0
  99. package/src/flip.ts +237 -0
  100. package/src/floating.ts +99 -0
  101. package/src/hsv.ts +100 -0
  102. package/src/icon-catalog.ts +52 -0
  103. package/src/image-zoom.ts +93 -0
  104. package/src/logo-paths.ts +21 -0
  105. package/src/matrix-axis.ts +91 -0
  106. package/src/pagination.ts +27 -0
  107. package/src/platform-keys.ts +29 -0
  108. package/src/rich-tokenize.ts +357 -0
  109. package/src/select-types.ts +32 -0
  110. package/src/slider-math.ts +45 -0
  111. package/src/sortable.ts +22 -0
  112. package/src/theme/fonts/OFL-Fraunces.txt +93 -0
  113. package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -0
  114. package/src/theme/fonts/OFL-Manrope.txt +93 -0
  115. package/src/theme/fonts/fraunces-latin-wght-normal.woff2 +0 -0
  116. package/src/theme/fonts/jetbrains-mono-latin.woff2 +0 -0
  117. package/src/theme/fonts/manrope-latin-wght-normal.woff2 +0 -0
  118. package/src/theme/looks.css +428 -0
  119. package/src/theme/palettes.css +254 -0
  120. package/src/theme/tokens.css +2894 -0
  121. package/src/tokens.ts +56 -0
  122. package/src/tooltip.css +43 -0
  123. package/src/tooltip.ts +146 -0
  124. package/src/useDialogFocus.ts +73 -0
  125. package/src/useGrowableField.ts +357 -0
  126. package/src/useModalKeyShield.ts +116 -0
  127. package/src/useToast.ts +48 -0
  128. package/src/wp-logo.svg +14 -0
@@ -0,0 +1,42 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * The NSFW and Featured badges on a post: small uppercase pills. Renders
4
+ * nothing for a safe, unfeatured post.
5
+ *
6
+ * <RatingBadge rating="nsfw" featured />
7
+ */
8
+ import Icon from "./Icon.vue";
9
+
10
+ defineProps<{
11
+ rating?: "sfw" | "nsfw" | undefined;
12
+ featured?: boolean | undefined;
13
+ }>();
14
+ </script>
15
+
16
+ <template>
17
+ <span v-if="rating === 'nsfw'" class="wp-rating-badge wp-rating-badge--nsfw">NSFW</span>
18
+ <span v-if="featured" class="wp-rating-badge wp-rating-badge--featured">
19
+ <Icon name="star-fill" :size="9" />Featured
20
+ </span>
21
+ </template>
22
+
23
+ <style scoped>
24
+ .wp-rating-badge {
25
+ --rating-badge-c: var(--wp-warn);
26
+ display: inline-flex;
27
+ align-items: center;
28
+ gap: 4px;
29
+ padding: 3px 8px;
30
+ border-radius: var(--wp-radius-cap, 999px);
31
+ font-size: 10px;
32
+ font-weight: 600;
33
+ letter-spacing: 0.08em;
34
+ line-height: 14px;
35
+ text-transform: uppercase;
36
+ white-space: nowrap;
37
+ color: var(--rating-badge-c);
38
+ background: color-mix(in oklab, var(--rating-badge-c) 16%, transparent);
39
+ border: 1px solid color-mix(in oklab, var(--rating-badge-c) 42%, transparent);
40
+ }
41
+ .wp-rating-badge--nsfw { --rating-badge-c: var(--wp-danger); color: var(--wp-danger-text); }
42
+ </style>
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+
4
+ const props = defineProps<{ value: string | null | undefined }>();
5
+
6
+ const display = computed(() => {
7
+ if (!props.value) return "—";
8
+ const ts = Date.parse(props.value);
9
+ if (Number.isNaN(ts)) return "—";
10
+ const diff = Date.now() - ts;
11
+ const sec = Math.floor(diff / 1000);
12
+ if (sec < 60) return "just now";
13
+ const min = Math.floor(sec / 60);
14
+ if (min < 60) return `${min}m ago`;
15
+ const hr = Math.floor(min / 60);
16
+ if (hr < 24) return `${hr}h ago`;
17
+ const day = Math.floor(hr / 24);
18
+ if (day < 7) return `${day}d ago`;
19
+ if (day < 30) return `${Math.floor(day / 7)}w ago`;
20
+ if (day < 365) return `${Math.floor(day / 30)}mo ago`;
21
+ return `${Math.floor(day / 365)}y ago`;
22
+ });
23
+ </script>
24
+
25
+ <template>
26
+ <span class="wp-reldate" :title="value || ''">{{ display }}</span>
27
+ </template>
28
+
29
+ <style scoped>
30
+ .wp-reldate {
31
+ font-size: 0.75rem;
32
+ line-height: 1rem;
33
+ color: var(--wp-text2);
34
+ }
35
+ </style>
@@ -0,0 +1,143 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Resize handle: the grip on a panel's edge that drags it wider or taller
4
+ * (a drawer's width, a column, a box's height). It owns the size in pixels
5
+ * through v-model; the parent puts that size on the panel. Works on the
6
+ * canvas and in the manager. Two panels side by side use SplitPane, which
7
+ * is built on this.
8
+ *
9
+ * <aside :style="{ width: `${w}px` }">…<ResizeHandle v-model="w" axis="x" reverse :min="280" :max="720" label="Chat width" /></aside>
10
+ *
11
+ * APG window splitter: `role="separator"` that takes focus; the arrows move
12
+ * it 16 px (Shift: 64), Home / End jump to min / max, Enter or a double
13
+ * click puts back `default-size`. `reverse` is for a handle on a panel's
14
+ * left or top edge, where dragging toward the start makes it bigger.
15
+ */
16
+ import { computed, ref } from "vue";
17
+
18
+ const props = withDefaults(defineProps<{
19
+ /** The panel's size in px. */
20
+ modelValue: number;
21
+ /** x: the handle changes width (an upright bar). y: height (a flat bar). */
22
+ axis?: "x" | "y" | undefined;
23
+ min?: number | undefined;
24
+ max?: number | undefined;
25
+ /** Dragging toward the start (left / up) makes the panel bigger. */
26
+ reverse?: boolean | undefined;
27
+ /** Where Enter and a double click put it back. */
28
+ defaultSize?: number | undefined;
29
+ /** Keyboard step in px. */
30
+ step?: number | undefined;
31
+ /** What it resizes, for screen readers ("Chat width"). */
32
+ label: string;
33
+ /** The id of the panel it resizes. */
34
+ controls?: string | undefined;
35
+ disabled?: boolean | undefined;
36
+ }>(), { axis: "x", min: 0, max: Number.POSITIVE_INFINITY, defaultSize: undefined, step: 16, controls: undefined });
37
+
38
+ const emit = defineEmits<{ (e: "update:modelValue", v: number): void; (e: "end", v: number): void }>();
39
+
40
+ const dragging = ref(false);
41
+ let start = 0;
42
+ let startSize = 0;
43
+
44
+ const clamp = (v: number): number => Math.round(Math.min(props.max, Math.max(props.min, v)));
45
+ const finiteMax = computed(() => (Number.isFinite(props.max) ? props.max : undefined));
46
+
47
+ function set(v: number): void {
48
+ const next = clamp(v);
49
+ if (next !== props.modelValue) emit("update:modelValue", next);
50
+ }
51
+
52
+ function onPointerDown(ev: PointerEvent): void {
53
+ if (props.disabled || ev.button !== 0) return;
54
+ ev.preventDefault();
55
+ dragging.value = true;
56
+ start = props.axis === "x" ? ev.clientX : ev.clientY;
57
+ startSize = props.modelValue;
58
+ (ev.currentTarget as HTMLElement).setPointerCapture?.(ev.pointerId);
59
+ }
60
+
61
+ function onPointerMove(ev: PointerEvent): void {
62
+ if (!dragging.value) return;
63
+ const d = (props.axis === "x" ? ev.clientX : ev.clientY) - start;
64
+ set(startSize + (props.reverse ? -d : d));
65
+ }
66
+
67
+ function onPointerUp(ev: PointerEvent): void {
68
+ if (!dragging.value) return;
69
+ dragging.value = false;
70
+ (ev.currentTarget as HTMLElement).releasePointerCapture?.(ev.pointerId);
71
+ emit("end", props.modelValue);
72
+ }
73
+
74
+ function reset(): void {
75
+ if (props.defaultSize === undefined || props.disabled) return;
76
+ set(props.defaultSize);
77
+ emit("end", clamp(props.defaultSize));
78
+ }
79
+
80
+ function onKeydown(ev: KeyboardEvent): void {
81
+ if (props.disabled) return;
82
+ const s = ev.shiftKey ? props.step * 4 : props.step;
83
+ const grow = props.reverse ? -1 : 1;
84
+ const fwd = props.axis === "x" ? "ArrowRight" : "ArrowDown";
85
+ const back = props.axis === "x" ? "ArrowLeft" : "ArrowUp";
86
+ let next: number;
87
+ if (ev.key === fwd) next = props.modelValue + s * grow;
88
+ else if (ev.key === back) next = props.modelValue - s * grow;
89
+ else if (ev.key === "Home") next = props.min;
90
+ else if (ev.key === "End" && finiteMax.value !== undefined) next = finiteMax.value;
91
+ else if (ev.key === "Enter") { ev.preventDefault(); reset(); return; }
92
+ else return;
93
+ ev.preventDefault();
94
+ set(next);
95
+ emit("end", clamp(next));
96
+ }
97
+ </script>
98
+
99
+ <!-- APG window splitter: a focusable separator is interactive. -->
100
+ <!-- eslint-disable vuejs-accessibility/no-static-element-interactions -->
101
+ <template>
102
+ <div
103
+ class="wp-resize"
104
+ role="separator"
105
+ :tabindex="disabled ? -1 : 0"
106
+ :aria-label="label"
107
+ :aria-orientation="axis === 'x' ? 'vertical' : 'horizontal'"
108
+ :aria-valuenow="modelValue"
109
+ :aria-valuemin="min"
110
+ :aria-valuemax="finiteMax"
111
+ :aria-controls="controls"
112
+ :aria-disabled="disabled || undefined"
113
+ :data-axis="axis"
114
+ :data-dragging="dragging || undefined"
115
+ @pointerdown="onPointerDown"
116
+ @pointermove="onPointerMove"
117
+ @pointerup="onPointerUp"
118
+ @pointercancel="onPointerUp"
119
+ @dblclick="reset"
120
+ @keydown="onKeydown"
121
+ />
122
+ </template>
123
+
124
+ <style scoped>
125
+ .wp-resize {
126
+ position: relative; flex: none; z-index: 1; outline: none; touch-action: none;
127
+ }
128
+ .wp-resize[data-axis="x"] { width: 8px; margin: 0 -4px; cursor: col-resize; align-self: stretch; }
129
+ .wp-resize[data-axis="y"] { height: 8px; margin: -4px 0; cursor: row-resize; width: 100%; }
130
+ .wp-resize::after {
131
+ content: ""; position: absolute; border-radius: min(1px, var(--wp-radius-cap, 999px)); background: transparent;
132
+ transition: background var(--wp-motion-hover);
133
+ }
134
+ .wp-resize[data-axis="x"]::after { top: 0; bottom: 0; left: 3px; width: 2px; }
135
+ .wp-resize[data-axis="y"]::after { left: 0; right: 0; top: 3px; height: 2px; }
136
+ .wp-resize:hover::after, .wp-resize[data-dragging]::after { background: var(--wp-accent); }
137
+ .wp-resize:focus-visible::after { background: var(--wp-border-focus); box-shadow: 0 0 0 1px var(--wp-border-focus); }
138
+ .wp-resize[aria-disabled="true"] { cursor: default; }
139
+ .wp-resize[aria-disabled="true"]::after { background: transparent; }
140
+ @media (prefers-reduced-motion: reduce) {
141
+ .wp-resize::after { transition: none; }
142
+ }
143
+ </style>
@@ -0,0 +1,199 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * The one search box: an inline magnifier, the text, an optional match count
4
+ * or shortcut, and a ✕ that clears it.
5
+ *
6
+ * It replaced three look-alikes (ListFilter's own box, the import/export
7
+ * PickerSearch, Input with a boxed search addon) and a dozen hand-built ones.
8
+ * Lives in `components/shared/` and reads only SHARED_TOKENS, so the canvas
9
+ * pickers and the manager draw the same box in their own colours. Canvas
10
+ * callers pass `size="sm"`.
11
+ *
12
+ * - Attributes (`data-test`, `@keydown`, `@focus`…) land on the `<input>`;
13
+ * `class`, `style` and `data-ai-*` stay on the box so a parent can size it.
14
+ * - `visible` + `total` show "3 of 12" while there is a query; add `noun` to
15
+ * also show "12 rules" when it's empty.
16
+ * - Escape with text clears it and stops there; an empty box lets Escape
17
+ * through so a dialog around it still closes.
18
+ * - The count and ✕ get `<testPrefix>-count` / `<testPrefix>-clear`, where
19
+ * `testPrefix` defaults to the input's own `data-test`.
20
+ * - `focus()` and `select()` are exposed for pickers that focus on open.
21
+ */
22
+ import Kbd from "./Kbd.vue";
23
+ import { computed, ref, useAttrs } from "vue";
24
+
25
+ defineOptions({ inheritAttrs: false });
26
+
27
+ const props = withDefaults(
28
+ defineProps<{
29
+ placeholder?: string | undefined;
30
+ ariaLabel?: string | undefined;
31
+ size?: "sm" | "md" | "lg" | undefined;
32
+ /** Rows after filtering. With `total`, shows "visible of total". */
33
+ visible?: number | undefined;
34
+ total?: number | undefined;
35
+ /** Plural noun for the idle count ("12 rules"). */
36
+ noun?: string | undefined;
37
+ /** A key hint shown while the box is empty, e.g. "/" or "Ctrl K". */
38
+ shortcut?: string | undefined;
39
+ clearLabel?: string | undefined;
40
+ disabled?: boolean | undefined;
41
+ testPrefix?: string | undefined;
42
+ }>(),
43
+ { placeholder: "Search…", size: "md", clearLabel: "Clear search" },
44
+ );
45
+
46
+ const query = defineModel<string>({ required: true });
47
+
48
+ const attrs = useAttrs();
49
+ /** The box keeps `class`, `style` and the AI pointer's `data-ai-*` marks (so
50
+ * the pointer rings the whole box); everything else goes to the input. */
51
+ const onRoot = (k: string): boolean => k === "class" || k === "style" || k.startsWith("data-ai-");
52
+ const rootAttrs = computed(() => Object.fromEntries(Object.entries(attrs).filter(([k]) => onRoot(k))));
53
+ const inputAttrs = computed(() => Object.fromEntries(Object.entries(attrs).filter(([k]) => !onRoot(k))));
54
+ const prefix = computed(() => props.testPrefix ?? (attrs["data-test"] as string | undefined));
55
+ const tid = (suffix: string): string | undefined => (prefix.value ? `${prefix.value}-${suffix}` : undefined);
56
+
57
+ const hasQuery = computed(() => query.value.trim().length > 0);
58
+ const counted = computed(() => props.total !== undefined && props.visible !== undefined);
59
+
60
+ const field = ref<HTMLInputElement | null>(null);
61
+
62
+ function clear(): void {
63
+ query.value = "";
64
+ field.value?.focus();
65
+ }
66
+
67
+ function onKeydown(ev: KeyboardEvent): void {
68
+ if (ev.key === "Escape" && query.value) {
69
+ ev.preventDefault();
70
+ ev.stopPropagation();
71
+ query.value = "";
72
+ }
73
+ }
74
+
75
+ defineExpose({
76
+ focus: () => field.value?.focus(),
77
+ select: () => field.value?.select(),
78
+ el: field,
79
+ });
80
+ </script>
81
+
82
+ <template>
83
+ <label
84
+ class="wp-search"
85
+ :class="[`wp-search--${size}`, { 'wp-search--on': hasQuery, 'is-disabled': disabled }]"
86
+ v-bind="rootAttrs"
87
+ >
88
+ <i class="pi pi-search wp-search__icon" aria-hidden="true" />
89
+ <input
90
+ ref="field"
91
+ v-model="query"
92
+ class="wp-search__field"
93
+ type="text"
94
+ role="searchbox"
95
+ :placeholder="placeholder"
96
+ :aria-label="ariaLabel ?? placeholder"
97
+ :disabled="disabled"
98
+ spellcheck="false"
99
+ autocomplete="off"
100
+ v-bind="inputAttrs"
101
+ @keydown="onKeydown"
102
+ />
103
+ <span
104
+ v-if="counted && (hasQuery || noun)"
105
+ class="wp-search__count"
106
+ :data-zero="hasQuery && visible === 0 ? '' : null"
107
+ :data-test="tid('count')"
108
+ aria-live="polite"
109
+ >
110
+ <template v-if="hasQuery">{{ visible }} of {{ total }}</template>
111
+ <template v-else>{{ total }} {{ noun }}</template>
112
+ </span>
113
+ <Kbd v-if="shortcut && !query" class="wp-search__kbd" :keys="shortcut" size="sm" aria-hidden="true" />
114
+ <button
115
+ v-if="query"
116
+ type="button"
117
+ class="wp-search__clear"
118
+ :aria-label="clearLabel"
119
+ :data-test="tid('clear')"
120
+ :disabled="disabled"
121
+ @click.prevent="clear"
122
+ >
123
+ <i class="pi pi-times" aria-hidden="true" />
124
+ </button>
125
+ </label>
126
+ </template>
127
+
128
+ <style scoped>
129
+ .wp-search {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: var(--wp-space-2);
133
+ min-width: 0;
134
+ height: var(--wp-control-h-md);
135
+ padding: 0 var(--wp-space-2) 0 var(--wp-space-3);
136
+ background: var(--wp-bg-2);
137
+ border: 1px solid var(--wp-border);
138
+ border-radius: var(--wp-radius-sm);
139
+ color: var(--wp-text);
140
+ font-family: var(--wp-font-sans);
141
+ font-size: var(--wp-text-sm);
142
+ cursor: text;
143
+ transition: border-color var(--wp-motion-quick), box-shadow var(--wp-motion-quick);
144
+ }
145
+ .wp-search--sm { height: var(--wp-control-h-sm); font-size: var(--wp-text-xs); gap: var(--wp-space-1); padding-left: var(--wp-space-2); }
146
+ .wp-search--lg { height: var(--wp-control-h-lg); font-size: var(--wp-text-base); padding-left: var(--wp-space-4); }
147
+ .wp-search:hover { border-color: var(--wp-border-strong); }
148
+ .wp-search:focus-within { border-color: var(--wp-border-focus); box-shadow: var(--wp-focus-ring); }
149
+ .wp-search--on { border-color: var(--wp-accent-500); }
150
+ .wp-search.is-disabled { opacity: 0.45; cursor: not-allowed; }
151
+
152
+ .wp-search__icon { flex-shrink: 0; font-size: 12px; line-height: 1; color: var(--wp-text-dim); }
153
+ .wp-search--sm .wp-search__icon { font-size: 10px; }
154
+ .wp-search--on .wp-search__icon,
155
+ .wp-search:focus-within .wp-search__icon { color: var(--wp-text-muted); }
156
+
157
+ .wp-search__field {
158
+ flex: 1 1 8ch;
159
+ min-width: 0;
160
+ height: 100%;
161
+ padding: 0;
162
+ border: 0;
163
+ background: none;
164
+ color: inherit;
165
+ font: inherit;
166
+ outline: none;
167
+ }
168
+ .wp-search__field::placeholder { color: var(--wp-text-dim); }
169
+ .wp-search__field:disabled { cursor: not-allowed; }
170
+
171
+ .wp-search__count {
172
+ flex-shrink: 0;
173
+ color: var(--wp-text-dim);
174
+ font-size: var(--wp-text-xs);
175
+ font-variant-numeric: tabular-nums;
176
+ white-space: nowrap;
177
+ }
178
+ .wp-search__count[data-zero] { color: var(--wp-danger); }
179
+
180
+ .wp-search__kbd { flex-shrink: 0; }
181
+
182
+ /* 20 px hit area even in the 24 px box; the mark draws at 9 px. */
183
+ .wp-search__clear {
184
+ display: grid;
185
+ place-items: center;
186
+ flex-shrink: 0;
187
+ width: 20px;
188
+ height: 20px;
189
+ padding: 0;
190
+ border: 0;
191
+ border-radius: var(--wp-radius-sm);
192
+ background: none;
193
+ color: var(--wp-text-dim);
194
+ cursor: pointer;
195
+ }
196
+ .wp-search__clear .pi { font-size: 9px; line-height: 1; }
197
+ .wp-search__clear:hover { color: var(--wp-text); background: var(--wp-bg-3); }
198
+ .wp-search__clear:focus-visible { outline: none; box-shadow: var(--wp-focus-ring); }
199
+ </style>