@dumiflex/wp-kit 0.3.0 → 0.4.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 +77 -51
- package/LICENSE +674 -674
- package/README.md +73 -76
- package/package.json +78 -77
- package/src/Avatar.vue +83 -83
- package/src/Badge.vue +97 -97
- package/src/Banner.vue +118 -118
- package/src/BookmarkButton.vue +27 -27
- package/src/BundleMeta.vue +51 -51
- package/src/Button.vue +103 -103
- package/src/Card.vue +68 -68
- package/src/Checkbox.vue +109 -107
- package/src/Chip.vue +163 -97
- package/src/ChoiceCards.vue +133 -133
- package/src/CloseButton.vue +69 -69
- package/src/CodeBlock.vue +119 -119
- package/src/ColorField.vue +188 -206
- package/src/ColorPicker.vue +201 -201
- package/src/Combobox.vue +331 -334
- package/src/ConfirmDialog.vue +165 -163
- package/src/ConfirmModal.vue +147 -147
- package/src/ConstraintMatrixPreview.vue +708 -708
- package/src/ContextMenu.vue +386 -379
- package/src/CopyButton.vue +156 -156
- package/src/DataTable.vue +292 -292
- package/src/DatePicker.vue +234 -234
- package/src/DescriptionList.vue +73 -73
- package/src/DiffView.vue +68 -68
- package/src/Disclosure.vue +166 -166
- package/src/DragHandle.vue +49 -49
- package/src/Drawer.vue +131 -131
- package/src/DropZone.vue +196 -196
- package/src/DropdownMenu.vue +72 -72
- package/src/EmptyState.vue +111 -111
- package/src/Field.vue +28 -28
- package/src/HsvPicker.vue +208 -208
- package/src/Icon.vue +39 -39
- package/src/IconPicker.vue +209 -209
- package/src/ImageGrid.vue +125 -125
- package/src/InlineEdit.vue +183 -183
- package/src/Input.vue +99 -99
- package/src/Kbd.vue +56 -56
- package/src/Lightbox.vue +173 -173
- package/src/ListFilter.vue +85 -85
- package/src/ListRow.vue +204 -204
- package/src/LoadingMark.vue +175 -161
- package/src/Logo.vue +60 -60
- package/src/Modal.vue +78 -76
- package/src/ModalShell.vue +109 -107
- package/src/Pagination.vue +106 -106
- package/src/Popover.vue +180 -171
- package/src/PostCard.vue +210 -211
- package/src/ProgressBar.vue +90 -90
- package/src/RadioGroup.vue +177 -177
- package/src/RangeSlider.vue +183 -183
- package/src/RelativeDate.vue +35 -35
- package/src/ResizeHandle.vue +143 -143
- package/src/SearchInput.vue +192 -199
- package/src/Select.vue +644 -641
- package/src/Separator.vue +54 -54
- package/src/Skeleton.vue +82 -82
- package/src/Slider.vue +198 -198
- package/src/SortableList.vue +260 -260
- package/src/SplitPane.vue +68 -68
- package/src/StarButton.vue +40 -40
- package/src/Stepper.vue +141 -141
- package/src/SupportLinks.vue +93 -93
- package/src/TagInput.vue +297 -300
- package/src/Textarea.vue +57 -57
- package/src/Timeline.vue +92 -92
- package/src/ToastCard.vue +119 -119
- package/src/ToastHost.vue +40 -40
- package/src/Toggle.vue +36 -36
- package/src/TokenizedValue.vue +310 -310
- package/src/Toolbar.vue +110 -110
- package/src/VirtualList.vue +87 -87
- package/src/VisuallyHidden.vue +29 -29
- package/src/WpButton.vue +75 -75
- package/src/WpCheck.vue +104 -102
- package/src/WpIconButton.vue +107 -107
- package/src/WpImageZoom.vue +544 -544
- package/src/WpNumber.vue +178 -183
- package/src/WpSegmented.vue +175 -175
- package/src/WpSelect.vue +93 -89
- package/src/WpSwitch.vue +163 -163
- package/src/WpTabs.vue +188 -188
- package/src/_modal-motion.css +33 -33
- package/src/_skeleton.css +50 -50
- package/src/axis-color.ts +42 -42
- package/src/button.css +169 -169
- package/src/calendar.ts +59 -59
- package/src/clipboard.ts +50 -50
- package/src/colour-presets.ts +12 -12
- package/src/diff.ts +51 -51
- package/src/drop-zone.ts +41 -41
- package/src/field.css +50 -0
- package/src/flip.ts +237 -237
- package/src/floating-layer.ts +48 -0
- package/src/floating.ts +99 -99
- package/src/hsv.ts +100 -100
- package/src/icon-catalog.ts +52 -52
- package/src/image-zoom.ts +93 -93
- package/src/kind-colour.ts +28 -0
- package/src/logo-paths.ts +21 -21
- package/src/matrix-axis.ts +91 -91
- package/src/pagination.ts +27 -27
- package/src/platform-keys.ts +29 -29
- package/src/rich-tokenize.ts +357 -357
- package/src/select-types.ts +32 -32
- package/src/slider-math.ts +45 -45
- package/src/sortable.ts +22 -22
- package/src/theme/fonts/OFL-Fraunces.txt +93 -93
- package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -93
- package/src/theme/fonts/OFL-Manrope.txt +93 -93
- package/src/theme/looks.css +428 -428
- package/src/theme/palettes.css +254 -254
- package/src/theme/tokens.css +2896 -2894
- package/src/tokens.ts +56 -56
- package/src/tooltip.css +43 -43
- package/src/tooltip.ts +146 -146
- package/src/useDialogFocus.ts +73 -73
- package/src/useGrowableField.ts +357 -357
- package/src/useModalKeyShield.ts +116 -116
- package/src/useToast.ts +48 -48
- package/src/wp-logo.svg +14 -14
- package/src/ChipToggle.vue +0 -92
- package/src/KindChip.vue +0 -53
- package/src/RatingBadge.vue +0 -42
package/src/HsvPicker.vue
CHANGED
|
@@ -1,208 +1,208 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* HsvPicker — saturation/value square + hue slider.
|
|
4
|
-
*
|
|
5
|
-
* Shared between manager ColorPicker / TweaksPanel AND the canvas
|
|
6
|
-
* bundle modal. CSS uses `--wp-*` tokens with hardcoded fallbacks so
|
|
7
|
-
* the picker renders inside the extension where the manager spacing
|
|
8
|
-
* tokens (`--wp-space-*`) aren't loaded.
|
|
9
|
-
*
|
|
10
|
-
* Pure CSS gradient render — no canvas. The SV square stacks three
|
|
11
|
-
* gradients to produce the HSV picker visual; the hue strip is a
|
|
12
|
-
* single multi-stop linear gradient across the spectrum.
|
|
13
|
-
*
|
|
14
|
-
* Drag is driven by document-level `pointermove` + `pointerup`
|
|
15
|
-
* listeners attached on pointerdown and detached on release. The
|
|
16
|
-
* earlier `setPointerCapture`-based approach locked up the hue
|
|
17
|
-
* slider when the SV picker reached an edge: in certain hosts
|
|
18
|
-
* (notably the canvas Teleport popover), capture wasn't always
|
|
19
|
-
* released cleanly, so subsequent pointerdown events on the hue
|
|
20
|
-
* slider got rerouted back to the SV element and the slider read
|
|
21
|
-
* as "stuck". Document-level handlers don't depend on element
|
|
22
|
-
* capture and always cleanly tear down on pointerup, even when the
|
|
23
|
-
* release happens outside the picker.
|
|
24
|
-
*/
|
|
25
|
-
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
|
26
|
-
import { hexToHsv, hsvToHex, type Hsv } from "./hsv";
|
|
27
|
-
|
|
28
|
-
interface Props {
|
|
29
|
-
modelValue: string;
|
|
30
|
-
ariaLabel?: string | undefined;
|
|
31
|
-
}
|
|
32
|
-
const props = defineProps<Props>();
|
|
33
|
-
|
|
34
|
-
const emit = defineEmits<{
|
|
35
|
-
(e: "update:modelValue", value: string): void;
|
|
36
|
-
}>();
|
|
37
|
-
|
|
38
|
-
const hsv = ref<Hsv>({ h: 0, s: 1, v: 1 });
|
|
39
|
-
|
|
40
|
-
function syncFromProp(hex: string) {
|
|
41
|
-
const parsed = hexToHsv(hex);
|
|
42
|
-
if (!parsed) return;
|
|
43
|
-
// Preserve the local hue when incoming RGB is pure black/white —
|
|
44
|
-
// otherwise the user's hue selection snaps to 0 every time s or v
|
|
45
|
-
// hits 0, which is jarring during a vertical drag.
|
|
46
|
-
if (parsed.s === 0) parsed.h = hsv.value.h;
|
|
47
|
-
hsv.value = parsed;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
watch(() => props.modelValue, syncFromProp, { immediate: true });
|
|
51
|
-
|
|
52
|
-
function emitHex() {
|
|
53
|
-
emit("update:modelValue", hsvToHex(hsv.value));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const hueBg = computed(() => `hsl(${hsv.value.h}, 100%, 50%)`);
|
|
57
|
-
|
|
58
|
-
const svEl = ref<HTMLDivElement | null>(null);
|
|
59
|
-
const hueEl = ref<HTMLDivElement | null>(null);
|
|
60
|
-
|
|
61
|
-
/** Document-level drag state. Only one drag can be active at a time
|
|
62
|
-
* (one mouse / one finger), so a single `activeDrag` ref tracking
|
|
63
|
-
* which surface owns the current pointer is sufficient. */
|
|
64
|
-
let activeDrag: "sv" | "hue" | null = null;
|
|
65
|
-
|
|
66
|
-
function handleSvMove(evt: PointerEvent) {
|
|
67
|
-
const el = svEl.value;
|
|
68
|
-
if (!el) return;
|
|
69
|
-
const rect = el.getBoundingClientRect();
|
|
70
|
-
const x = Math.max(0, Math.min(rect.width, evt.clientX - rect.left));
|
|
71
|
-
const y = Math.max(0, Math.min(rect.height, evt.clientY - rect.top));
|
|
72
|
-
hsv.value = { h: hsv.value.h, s: x / rect.width, v: 1 - (y / rect.height) };
|
|
73
|
-
emitHex();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function handleHueMove(evt: PointerEvent) {
|
|
77
|
-
const el = hueEl.value;
|
|
78
|
-
if (!el) return;
|
|
79
|
-
const rect = el.getBoundingClientRect();
|
|
80
|
-
const x = Math.max(0, Math.min(rect.width, evt.clientX - rect.left));
|
|
81
|
-
hsv.value = { h: (x / rect.width) * 360, s: hsv.value.s, v: hsv.value.v };
|
|
82
|
-
emitHex();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function onDocPointerMove(evt: PointerEvent) {
|
|
86
|
-
if (activeDrag === "sv") handleSvMove(evt);
|
|
87
|
-
else if (activeDrag === "hue") handleHueMove(evt);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function onDocPointerUp() {
|
|
91
|
-
activeDrag = null;
|
|
92
|
-
document.removeEventListener("pointermove", onDocPointerMove);
|
|
93
|
-
document.removeEventListener("pointerup", onDocPointerUp);
|
|
94
|
-
document.removeEventListener("pointercancel", onDocPointerUp);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function startDrag(kind: "sv" | "hue", evt: PointerEvent) {
|
|
98
|
-
activeDrag = kind;
|
|
99
|
-
if (kind === "sv") handleSvMove(evt);
|
|
100
|
-
else handleHueMove(evt);
|
|
101
|
-
document.addEventListener("pointermove", onDocPointerMove);
|
|
102
|
-
document.addEventListener("pointerup", onDocPointerUp);
|
|
103
|
-
document.addEventListener("pointercancel", onDocPointerUp);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function onSvPointerDown(evt: PointerEvent) {
|
|
107
|
-
evt.preventDefault();
|
|
108
|
-
startDrag("sv", evt);
|
|
109
|
-
}
|
|
110
|
-
function onHuePointerDown(evt: PointerEvent) {
|
|
111
|
-
evt.preventDefault();
|
|
112
|
-
startDrag("hue", evt);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
onBeforeUnmount(onDocPointerUp);
|
|
116
|
-
|
|
117
|
-
const svCursorStyle = computed(() => ({
|
|
118
|
-
left: `${hsv.value.s * 100}%`,
|
|
119
|
-
top: `${(1 - hsv.value.v) * 100}%`,
|
|
120
|
-
}));
|
|
121
|
-
const hueCursorStyle = computed(() => ({
|
|
122
|
-
left: `${(hsv.value.h / 360) * 100}%`,
|
|
123
|
-
}));
|
|
124
|
-
</script>
|
|
125
|
-
|
|
126
|
-
<template>
|
|
127
|
-
<div class="wp-hsv-picker" :aria-label="ariaLabel">
|
|
128
|
-
<div
|
|
129
|
-
ref="svEl"
|
|
130
|
-
class="wp-hsv-picker__sv"
|
|
131
|
-
:style="{ '--wp-hsv-hue': hueBg }"
|
|
132
|
-
:aria-label="`Saturation and brightness, current ${Math.round(hsv.s * 100)}% / ${Math.round(hsv.v * 100)}%`"
|
|
133
|
-
data-test="hsv-sv"
|
|
134
|
-
@pointerdown="onSvPointerDown"
|
|
135
|
-
>
|
|
136
|
-
<div class="wp-hsv-picker__sv-cursor" :style="svCursorStyle" />
|
|
137
|
-
</div>
|
|
138
|
-
<div
|
|
139
|
-
ref="hueEl"
|
|
140
|
-
class="wp-hsv-picker__hue"
|
|
141
|
-
:aria-label="`Hue, current ${Math.round(hsv.h)}°`"
|
|
142
|
-
data-test="hsv-hue"
|
|
143
|
-
@pointerdown="onHuePointerDown"
|
|
144
|
-
>
|
|
145
|
-
<div class="wp-hsv-picker__hue-cursor" :style="hueCursorStyle" />
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
</template>
|
|
149
|
-
|
|
150
|
-
<style scoped>
|
|
151
|
-
.wp-hsv-picker { display: flex; flex-direction: column; gap: var(--wp-space-4, 8px); width: 100%; }
|
|
152
|
-
.wp-hsv-picker__sv {
|
|
153
|
-
position: relative;
|
|
154
|
-
width: 100%;
|
|
155
|
-
height: 120px;
|
|
156
|
-
border-radius: var(--wp-radius-sm, 4px);
|
|
157
|
-
border: 1px solid var(--wp-border, #3a3a3a);
|
|
158
|
-
cursor: crosshair;
|
|
159
|
-
touch-action: none;
|
|
160
|
-
user-select: none;
|
|
161
|
-
background:
|
|
162
|
-
linear-gradient(to top, #000, transparent),
|
|
163
|
-
linear-gradient(to right, #fff, transparent),
|
|
164
|
-
var(--wp-hsv-hue, hsl(0, 100%, 50%));
|
|
165
|
-
}
|
|
166
|
-
.wp-hsv-picker__sv-cursor {
|
|
167
|
-
position: absolute;
|
|
168
|
-
width: 14px;
|
|
169
|
-
height: 14px;
|
|
170
|
-
border-radius: 50%; /* round: picker cursor on the colour field */
|
|
171
|
-
border: 2px solid #fff;
|
|
172
|
-
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 1px 2px rgba(0, 0, 0, 0.45);
|
|
173
|
-
transform: translate(-50%, -50%);
|
|
174
|
-
pointer-events: none;
|
|
175
|
-
}
|
|
176
|
-
.wp-hsv-picker__hue {
|
|
177
|
-
position: relative;
|
|
178
|
-
width: 100%;
|
|
179
|
-
height: 12px;
|
|
180
|
-
border-radius: var(--wp-radius-sm, 4px);
|
|
181
|
-
border: 1px solid var(--wp-border, #3a3a3a);
|
|
182
|
-
cursor: ew-resize;
|
|
183
|
-
touch-action: none;
|
|
184
|
-
user-select: none;
|
|
185
|
-
background: linear-gradient(
|
|
186
|
-
to right,
|
|
187
|
-
hsl(0, 100%, 50%),
|
|
188
|
-
hsl(60, 100%, 50%),
|
|
189
|
-
hsl(120, 100%, 50%),
|
|
190
|
-
hsl(180, 100%, 50%),
|
|
191
|
-
hsl(240, 100%, 50%),
|
|
192
|
-
hsl(300, 100%, 50%),
|
|
193
|
-
hsl(360, 100%, 50%)
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
.wp-hsv-picker__hue-cursor {
|
|
197
|
-
position: absolute;
|
|
198
|
-
top: 50%;
|
|
199
|
-
width: 12px;
|
|
200
|
-
height: 16px;
|
|
201
|
-
border-radius: var(--wp-radius-sm, 4px);
|
|
202
|
-
border: 2px solid #fff;
|
|
203
|
-
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 1px 2px rgba(0, 0, 0, 0.45);
|
|
204
|
-
transform: translate(-50%, -50%);
|
|
205
|
-
pointer-events: none;
|
|
206
|
-
background: transparent;
|
|
207
|
-
}
|
|
208
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* HsvPicker — saturation/value square + hue slider.
|
|
4
|
+
*
|
|
5
|
+
* Shared between manager ColorPicker / TweaksPanel AND the canvas
|
|
6
|
+
* bundle modal. CSS uses `--wp-*` tokens with hardcoded fallbacks so
|
|
7
|
+
* the picker renders inside the extension where the manager spacing
|
|
8
|
+
* tokens (`--wp-space-*`) aren't loaded.
|
|
9
|
+
*
|
|
10
|
+
* Pure CSS gradient render — no canvas. The SV square stacks three
|
|
11
|
+
* gradients to produce the HSV picker visual; the hue strip is a
|
|
12
|
+
* single multi-stop linear gradient across the spectrum.
|
|
13
|
+
*
|
|
14
|
+
* Drag is driven by document-level `pointermove` + `pointerup`
|
|
15
|
+
* listeners attached on pointerdown and detached on release. The
|
|
16
|
+
* earlier `setPointerCapture`-based approach locked up the hue
|
|
17
|
+
* slider when the SV picker reached an edge: in certain hosts
|
|
18
|
+
* (notably the canvas Teleport popover), capture wasn't always
|
|
19
|
+
* released cleanly, so subsequent pointerdown events on the hue
|
|
20
|
+
* slider got rerouted back to the SV element and the slider read
|
|
21
|
+
* as "stuck". Document-level handlers don't depend on element
|
|
22
|
+
* capture and always cleanly tear down on pointerup, even when the
|
|
23
|
+
* release happens outside the picker.
|
|
24
|
+
*/
|
|
25
|
+
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
|
26
|
+
import { hexToHsv, hsvToHex, type Hsv } from "./hsv";
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
modelValue: string;
|
|
30
|
+
ariaLabel?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
const props = defineProps<Props>();
|
|
33
|
+
|
|
34
|
+
const emit = defineEmits<{
|
|
35
|
+
(e: "update:modelValue", value: string): void;
|
|
36
|
+
}>();
|
|
37
|
+
|
|
38
|
+
const hsv = ref<Hsv>({ h: 0, s: 1, v: 1 });
|
|
39
|
+
|
|
40
|
+
function syncFromProp(hex: string) {
|
|
41
|
+
const parsed = hexToHsv(hex);
|
|
42
|
+
if (!parsed) return;
|
|
43
|
+
// Preserve the local hue when incoming RGB is pure black/white —
|
|
44
|
+
// otherwise the user's hue selection snaps to 0 every time s or v
|
|
45
|
+
// hits 0, which is jarring during a vertical drag.
|
|
46
|
+
if (parsed.s === 0) parsed.h = hsv.value.h;
|
|
47
|
+
hsv.value = parsed;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
watch(() => props.modelValue, syncFromProp, { immediate: true });
|
|
51
|
+
|
|
52
|
+
function emitHex() {
|
|
53
|
+
emit("update:modelValue", hsvToHex(hsv.value));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const hueBg = computed(() => `hsl(${hsv.value.h}, 100%, 50%)`);
|
|
57
|
+
|
|
58
|
+
const svEl = ref<HTMLDivElement | null>(null);
|
|
59
|
+
const hueEl = ref<HTMLDivElement | null>(null);
|
|
60
|
+
|
|
61
|
+
/** Document-level drag state. Only one drag can be active at a time
|
|
62
|
+
* (one mouse / one finger), so a single `activeDrag` ref tracking
|
|
63
|
+
* which surface owns the current pointer is sufficient. */
|
|
64
|
+
let activeDrag: "sv" | "hue" | null = null;
|
|
65
|
+
|
|
66
|
+
function handleSvMove(evt: PointerEvent) {
|
|
67
|
+
const el = svEl.value;
|
|
68
|
+
if (!el) return;
|
|
69
|
+
const rect = el.getBoundingClientRect();
|
|
70
|
+
const x = Math.max(0, Math.min(rect.width, evt.clientX - rect.left));
|
|
71
|
+
const y = Math.max(0, Math.min(rect.height, evt.clientY - rect.top));
|
|
72
|
+
hsv.value = { h: hsv.value.h, s: x / rect.width, v: 1 - (y / rect.height) };
|
|
73
|
+
emitHex();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function handleHueMove(evt: PointerEvent) {
|
|
77
|
+
const el = hueEl.value;
|
|
78
|
+
if (!el) return;
|
|
79
|
+
const rect = el.getBoundingClientRect();
|
|
80
|
+
const x = Math.max(0, Math.min(rect.width, evt.clientX - rect.left));
|
|
81
|
+
hsv.value = { h: (x / rect.width) * 360, s: hsv.value.s, v: hsv.value.v };
|
|
82
|
+
emitHex();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function onDocPointerMove(evt: PointerEvent) {
|
|
86
|
+
if (activeDrag === "sv") handleSvMove(evt);
|
|
87
|
+
else if (activeDrag === "hue") handleHueMove(evt);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function onDocPointerUp() {
|
|
91
|
+
activeDrag = null;
|
|
92
|
+
document.removeEventListener("pointermove", onDocPointerMove);
|
|
93
|
+
document.removeEventListener("pointerup", onDocPointerUp);
|
|
94
|
+
document.removeEventListener("pointercancel", onDocPointerUp);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function startDrag(kind: "sv" | "hue", evt: PointerEvent) {
|
|
98
|
+
activeDrag = kind;
|
|
99
|
+
if (kind === "sv") handleSvMove(evt);
|
|
100
|
+
else handleHueMove(evt);
|
|
101
|
+
document.addEventListener("pointermove", onDocPointerMove);
|
|
102
|
+
document.addEventListener("pointerup", onDocPointerUp);
|
|
103
|
+
document.addEventListener("pointercancel", onDocPointerUp);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function onSvPointerDown(evt: PointerEvent) {
|
|
107
|
+
evt.preventDefault();
|
|
108
|
+
startDrag("sv", evt);
|
|
109
|
+
}
|
|
110
|
+
function onHuePointerDown(evt: PointerEvent) {
|
|
111
|
+
evt.preventDefault();
|
|
112
|
+
startDrag("hue", evt);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
onBeforeUnmount(onDocPointerUp);
|
|
116
|
+
|
|
117
|
+
const svCursorStyle = computed(() => ({
|
|
118
|
+
left: `${hsv.value.s * 100}%`,
|
|
119
|
+
top: `${(1 - hsv.value.v) * 100}%`,
|
|
120
|
+
}));
|
|
121
|
+
const hueCursorStyle = computed(() => ({
|
|
122
|
+
left: `${(hsv.value.h / 360) * 100}%`,
|
|
123
|
+
}));
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<template>
|
|
127
|
+
<div class="wp-hsv-picker" :aria-label="ariaLabel">
|
|
128
|
+
<div
|
|
129
|
+
ref="svEl"
|
|
130
|
+
class="wp-hsv-picker__sv"
|
|
131
|
+
:style="{ '--wp-hsv-hue': hueBg }"
|
|
132
|
+
:aria-label="`Saturation and brightness, current ${Math.round(hsv.s * 100)}% / ${Math.round(hsv.v * 100)}%`"
|
|
133
|
+
data-test="hsv-sv"
|
|
134
|
+
@pointerdown="onSvPointerDown"
|
|
135
|
+
>
|
|
136
|
+
<div class="wp-hsv-picker__sv-cursor" :style="svCursorStyle" />
|
|
137
|
+
</div>
|
|
138
|
+
<div
|
|
139
|
+
ref="hueEl"
|
|
140
|
+
class="wp-hsv-picker__hue"
|
|
141
|
+
:aria-label="`Hue, current ${Math.round(hsv.h)}°`"
|
|
142
|
+
data-test="hsv-hue"
|
|
143
|
+
@pointerdown="onHuePointerDown"
|
|
144
|
+
>
|
|
145
|
+
<div class="wp-hsv-picker__hue-cursor" :style="hueCursorStyle" />
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</template>
|
|
149
|
+
|
|
150
|
+
<style scoped>
|
|
151
|
+
.wp-hsv-picker { display: flex; flex-direction: column; gap: var(--wp-space-4, 8px); width: 100%; }
|
|
152
|
+
.wp-hsv-picker__sv {
|
|
153
|
+
position: relative;
|
|
154
|
+
width: 100%;
|
|
155
|
+
height: 120px;
|
|
156
|
+
border-radius: var(--wp-radius-sm, 4px);
|
|
157
|
+
border: 1px solid var(--wp-border, #3a3a3a);
|
|
158
|
+
cursor: crosshair;
|
|
159
|
+
touch-action: none;
|
|
160
|
+
user-select: none;
|
|
161
|
+
background:
|
|
162
|
+
linear-gradient(to top, #000, transparent),
|
|
163
|
+
linear-gradient(to right, #fff, transparent),
|
|
164
|
+
var(--wp-hsv-hue, hsl(0, 100%, 50%));
|
|
165
|
+
}
|
|
166
|
+
.wp-hsv-picker__sv-cursor {
|
|
167
|
+
position: absolute;
|
|
168
|
+
width: 14px;
|
|
169
|
+
height: 14px;
|
|
170
|
+
border-radius: 50%; /* round: picker cursor on the colour field */
|
|
171
|
+
border: 2px solid #fff;
|
|
172
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 1px 2px rgba(0, 0, 0, 0.45);
|
|
173
|
+
transform: translate(-50%, -50%);
|
|
174
|
+
pointer-events: none;
|
|
175
|
+
}
|
|
176
|
+
.wp-hsv-picker__hue {
|
|
177
|
+
position: relative;
|
|
178
|
+
width: 100%;
|
|
179
|
+
height: 12px;
|
|
180
|
+
border-radius: var(--wp-radius-sm, 4px);
|
|
181
|
+
border: 1px solid var(--wp-border, #3a3a3a);
|
|
182
|
+
cursor: ew-resize;
|
|
183
|
+
touch-action: none;
|
|
184
|
+
user-select: none;
|
|
185
|
+
background: linear-gradient(
|
|
186
|
+
to right,
|
|
187
|
+
hsl(0, 100%, 50%),
|
|
188
|
+
hsl(60, 100%, 50%),
|
|
189
|
+
hsl(120, 100%, 50%),
|
|
190
|
+
hsl(180, 100%, 50%),
|
|
191
|
+
hsl(240, 100%, 50%),
|
|
192
|
+
hsl(300, 100%, 50%),
|
|
193
|
+
hsl(360, 100%, 50%)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
.wp-hsv-picker__hue-cursor {
|
|
197
|
+
position: absolute;
|
|
198
|
+
top: 50%;
|
|
199
|
+
width: 12px;
|
|
200
|
+
height: 16px;
|
|
201
|
+
border-radius: var(--wp-radius-sm, 4px);
|
|
202
|
+
border: 2px solid #fff;
|
|
203
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 1px 2px rgba(0, 0, 0, 0.45);
|
|
204
|
+
transform: translate(-50%, -50%);
|
|
205
|
+
pointer-events: none;
|
|
206
|
+
background: transparent;
|
|
207
|
+
}
|
|
208
|
+
</style>
|
package/src/Icon.vue
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Canonical icon sizes. Use these instead of raw numbers on `:size`.
|
|
4
|
-
*
|
|
5
|
-
* ICON_SM (12) — micro indicators, badge glyphs, tight pill icons
|
|
6
|
-
* ICON_MD (14) — default toolbar / sidebar / inline icons
|
|
7
|
-
* ICON_LG (18) — emphasized icons (CTA buttons, empty-state subheadings,
|
|
8
|
-
* panel-header glyphs)
|
|
9
|
-
*
|
|
10
|
-
* Outliers (e.g. very large empty-state icons at 48-80px) stay as raw
|
|
11
|
-
* numbers — flag the locations in PR review so we can decide whether
|
|
12
|
-
* to grow the scale.
|
|
13
|
-
*/
|
|
14
|
-
export const ICON_SM = 12;
|
|
15
|
-
export const ICON_MD = 14;
|
|
16
|
-
export const ICON_LG = 18;
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<script setup lang="ts">
|
|
20
|
-
import { computed } from "vue";
|
|
21
|
-
|
|
22
|
-
interface Props {
|
|
23
|
-
name: string;
|
|
24
|
-
size?: number | undefined;
|
|
25
|
-
}
|
|
26
|
-
const props = defineProps<Props>();
|
|
27
|
-
|
|
28
|
-
const className = computed(() => {
|
|
29
|
-
// Accept either bare name ("plus", "check-circle") or already-prefixed ("pi-plus").
|
|
30
|
-
const n = props.name.startsWith("pi-") ? props.name : `pi-${props.name}`;
|
|
31
|
-
return `pi ${n}`;
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const style = computed(() => (props.size ? { fontSize: `${props.size}px` } : undefined));
|
|
35
|
-
</script>
|
|
36
|
-
|
|
37
|
-
<template>
|
|
38
|
-
<i :class="className" :style="style" aria-hidden="true" />
|
|
39
|
-
</template>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Canonical icon sizes. Use these instead of raw numbers on `:size`.
|
|
4
|
+
*
|
|
5
|
+
* ICON_SM (12) — micro indicators, badge glyphs, tight pill icons
|
|
6
|
+
* ICON_MD (14) — default toolbar / sidebar / inline icons
|
|
7
|
+
* ICON_LG (18) — emphasized icons (CTA buttons, empty-state subheadings,
|
|
8
|
+
* panel-header glyphs)
|
|
9
|
+
*
|
|
10
|
+
* Outliers (e.g. very large empty-state icons at 48-80px) stay as raw
|
|
11
|
+
* numbers — flag the locations in PR review so we can decide whether
|
|
12
|
+
* to grow the scale.
|
|
13
|
+
*/
|
|
14
|
+
export const ICON_SM = 12;
|
|
15
|
+
export const ICON_MD = 14;
|
|
16
|
+
export const ICON_LG = 18;
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { computed } from "vue";
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
name: string;
|
|
24
|
+
size?: number | undefined;
|
|
25
|
+
}
|
|
26
|
+
const props = defineProps<Props>();
|
|
27
|
+
|
|
28
|
+
const className = computed(() => {
|
|
29
|
+
// Accept either bare name ("plus", "check-circle") or already-prefixed ("pi-plus").
|
|
30
|
+
const n = props.name.startsWith("pi-") ? props.name : `pi-${props.name}`;
|
|
31
|
+
return `pi ${n}`;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const style = computed(() => (props.size ? { fontSize: `${props.size}px` } : undefined));
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<i :class="className" :style="style" aria-hidden="true" />
|
|
39
|
+
</template>
|