@dumiflex/wp-kit 0.11.0 → 0.13.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 +14 -0
- package/package.json +1 -1
- package/src/Input.vue +8 -1
- package/src/SettingRow.vue +7 -0
- package/src/Textarea.vue +6 -0
- package/src/input.css +26 -1
- package/src/toggle-row.css +7 -0
- package/src/toggle-row.ts +104 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.0](https://github.com/DumiFlex/wp-kit/compare/v0.12.0...v0.13.0) (2026-11-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* v-toggle-row, so a row's words flip its one checkbox or switch, and SettingRow rows do it ([#102](https://github.com/DumiFlex/wp-kit/issues/102)) ([1d3309b](https://github.com/DumiFlex/wp-kit/commit/1d3309b6f2a7da6f2dff82a41aed8bc46d1c779a))
|
|
9
|
+
|
|
10
|
+
## [0.12.0](https://github.com/DumiFlex/wp-kit/compare/v0.11.0...v0.12.0) (2026-11-03)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* Input and Textarea take mono and an accent / warn tone, and size themselves border-box ([#98](https://github.com/DumiFlex/wp-kit/issues/98)) ([7b1023f](https://github.com/DumiFlex/wp-kit/commit/7b1023f699e7f9b37904abf0200ed805f1db23a7))
|
|
16
|
+
|
|
3
17
|
## [0.11.0](https://github.com/DumiFlex/wp-kit/compare/v0.10.0...v0.11.0) (2026-11-03)
|
|
4
18
|
|
|
5
19
|
|
package/package.json
CHANGED
package/src/Input.vue
CHANGED
|
@@ -24,6 +24,10 @@ interface Props {
|
|
|
24
24
|
suffix?: string | undefined;
|
|
25
25
|
id?: string | undefined;
|
|
26
26
|
ariaLabel?: string | undefined;
|
|
27
|
+
/** Mono text: variable names, ids, tags. */
|
|
28
|
+
mono?: boolean | undefined;
|
|
29
|
+
/** "accent" marks a value changed from its default, "warn" one that needs a look. */
|
|
30
|
+
tone?: "accent" | "warn" | undefined;
|
|
27
31
|
}
|
|
28
32
|
const props = withDefaults(defineProps<Props>(), {
|
|
29
33
|
type: "text",
|
|
@@ -57,7 +61,7 @@ function onInput(e: Event) {
|
|
|
57
61
|
</script>
|
|
58
62
|
|
|
59
63
|
<template>
|
|
60
|
-
<div v-if="wrapped" :class="groupClasses" :aria-invalid="error || undefined">
|
|
64
|
+
<div v-if="wrapped" :class="groupClasses" :aria-invalid="error || undefined" :data-tone="tone">
|
|
61
65
|
<span v-if="icon" class="wp-input-group__icon" aria-hidden="true">
|
|
62
66
|
<Icon :name="icon" />
|
|
63
67
|
</span>
|
|
@@ -65,6 +69,7 @@ function onInput(e: Event) {
|
|
|
65
69
|
<input
|
|
66
70
|
:id="id"
|
|
67
71
|
:class="inputClasses"
|
|
72
|
+
:data-mono="mono || undefined"
|
|
68
73
|
:type="type"
|
|
69
74
|
:value="modelValue"
|
|
70
75
|
:placeholder="placeholder"
|
|
@@ -85,6 +90,8 @@ function onInput(e: Event) {
|
|
|
85
90
|
v-else
|
|
86
91
|
:id="id"
|
|
87
92
|
:class="inputClasses"
|
|
93
|
+
:data-mono="mono || undefined"
|
|
94
|
+
:data-tone="tone"
|
|
88
95
|
:type="type"
|
|
89
96
|
:value="modelValue"
|
|
90
97
|
:placeholder="placeholder"
|
package/src/SettingRow.vue
CHANGED
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
* control column, like a form. `recommended` names the value most people
|
|
15
15
|
* should pick ("On"). `for` makes the label a real `<label for>`. Slots:
|
|
16
16
|
* `#badge` after the label, `#extra` under the hint (a status line).
|
|
17
|
+
*
|
|
18
|
+
* A row whose control is one checkbox or switch flips it on a click anywhere
|
|
19
|
+
* on the row's words (v-toggle-row), like a label; a WpSwitch is a span, so a
|
|
20
|
+
* `<label for>` alone can't reach it.
|
|
17
21
|
*/
|
|
22
|
+
import { vToggleRow } from "./toggle-row";
|
|
23
|
+
|
|
18
24
|
const props = defineProps<{
|
|
19
25
|
label: string;
|
|
20
26
|
hint?: string | undefined;
|
|
@@ -32,6 +38,7 @@ const props = defineProps<{
|
|
|
32
38
|
<template>
|
|
33
39
|
<div
|
|
34
40
|
:id="settingKey"
|
|
41
|
+
v-toggle-row
|
|
35
42
|
class="wp-set-row"
|
|
36
43
|
:class="{ 'wp-set-row--stacked': stacked, 'wp-set-row--form': form, 'wp-set-row--target': highlighted }"
|
|
37
44
|
:data-setting="settingKey"
|
package/src/Textarea.vue
CHANGED
|
@@ -10,6 +10,10 @@ interface Props {
|
|
|
10
10
|
error?: boolean | undefined;
|
|
11
11
|
id?: string | undefined;
|
|
12
12
|
ariaLabel?: string | undefined;
|
|
13
|
+
/** Mono text: templates, tag lists. */
|
|
14
|
+
mono?: boolean | undefined;
|
|
15
|
+
/** "accent" marks a value changed from its default, "warn" one that needs a look. */
|
|
16
|
+
tone?: "accent" | "warn" | undefined;
|
|
13
17
|
}
|
|
14
18
|
const props = withDefaults(defineProps<Props>(), { rows: 3 });
|
|
15
19
|
|
|
@@ -44,6 +48,8 @@ watch(() => props.modelValue, () => {
|
|
|
44
48
|
:id="id"
|
|
45
49
|
ref="taRef"
|
|
46
50
|
class="wp-textarea"
|
|
51
|
+
:data-mono="mono || undefined"
|
|
52
|
+
:data-tone="tone"
|
|
47
53
|
:rows="rows"
|
|
48
54
|
:value="modelValue"
|
|
49
55
|
:placeholder="placeholder"
|
package/src/input.css
CHANGED
|
@@ -3,7 +3,15 @@
|
|
|
3
3
|
* Scoped into Input.vue and Textarea.vue so a field looks right on the
|
|
4
4
|
* canvas, which never loads theme/tokens.css; the theme imports it too so
|
|
5
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`.
|
|
6
|
+
* tokens only. The other field pieces draw field.css' `.wp-field-box`.
|
|
7
|
+
*
|
|
8
|
+
* `data-mono` sets the text in the mono face (variable names, ids, tags);
|
|
9
|
+
* `data-tone="accent"` marks a value changed from its default (accent border
|
|
10
|
+
* and text), `data-tone="warn"` one that needs a look (an empty binding). */
|
|
11
|
+
|
|
12
|
+
.wp-input,
|
|
13
|
+
.wp-textarea,
|
|
14
|
+
.wp-input-group { box-sizing: border-box; }
|
|
7
15
|
|
|
8
16
|
.wp-input,
|
|
9
17
|
.wp-textarea {
|
|
@@ -111,3 +119,20 @@
|
|
|
111
119
|
flex: 1;
|
|
112
120
|
}
|
|
113
121
|
.wp-input-group .wp-input:focus { box-shadow: none; }
|
|
122
|
+
|
|
123
|
+
/* Mono text and tones (see the top of this file). On a prefixed field the
|
|
124
|
+
* group draws the border, so the tone sits on the group. */
|
|
125
|
+
.wp-input[data-mono],
|
|
126
|
+
.wp-textarea[data-mono] { font-family: var(--wp-font-mono); font-size: 12px; }
|
|
127
|
+
.wp-input--sm[data-mono] { font-size: 11px; }
|
|
128
|
+
.wp-input[data-tone="accent"],
|
|
129
|
+
.wp-textarea[data-tone="accent"],
|
|
130
|
+
.wp-input-group[data-tone="accent"] { border-color: var(--wp-accent-500); }
|
|
131
|
+
.wp-input[data-tone="accent"],
|
|
132
|
+
.wp-textarea[data-tone="accent"],
|
|
133
|
+
.wp-input-group[data-tone="accent"] .wp-input,
|
|
134
|
+
.wp-input-group[data-tone="accent"] .wp-input-group__affix { color: var(--wp-accent-text); }
|
|
135
|
+
.wp-input[data-tone="warn"],
|
|
136
|
+
.wp-textarea[data-tone="warn"],
|
|
137
|
+
.wp-input-group[data-tone="warn"] { border-color: var(--wp-warn); }
|
|
138
|
+
.wp-input-group[data-tone="warn"] .wp-input-group__affix { color: var(--wp-warn); }
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* v-toggle-row (toggle-row.ts): the row's words flip its one toggle, so the
|
|
2
|
+
* whole row shows the hand and its words don't get picked up as a selection
|
|
3
|
+
* on a quick double click. Layered, so a host's own cursor rule wins. */
|
|
4
|
+
@layer wp-extension {
|
|
5
|
+
.wp-toggle-row { cursor: pointer; user-select: none; -webkit-user-select: none; }
|
|
6
|
+
.wp-toggle-row--disabled { cursor: not-allowed; }
|
|
7
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `v-toggle-row`: a click on a row's words flips the row's one checkbox or
|
|
3
|
+
* switch, the way a `<label>` ticks its input. For a row that lays out its
|
|
4
|
+
* label and its control itself (a label on the left, a switch on the right)
|
|
5
|
+
* instead of passing the words to the Checkbox or WpSwitch.
|
|
6
|
+
*
|
|
7
|
+
* <div v-toggle-row class="row">
|
|
8
|
+
* <span>Override base seed</span>
|
|
9
|
+
* <WpSwitch v-model="override" aria-label="Override base seed" />
|
|
10
|
+
* </div>
|
|
11
|
+
*
|
|
12
|
+
* It acts only when the row holds exactly one `role="checkbox"` or
|
|
13
|
+
* `role="switch"` and no other value control (an input, a select, a slider,
|
|
14
|
+
* radios), so a row with two switches or a switch beside a number field stays
|
|
15
|
+
* as it is. A click on anything interactive inside the row (the toggle
|
|
16
|
+
* itself, a button, a link, a native `<label>`, a labelled Checkbox) is left
|
|
17
|
+
* to that element. The control keeps the keyboard stop and gets the focus.
|
|
18
|
+
* SettingRow uses it on every row.
|
|
19
|
+
*
|
|
20
|
+
* The row gets the `wp-toggle-row` class while it has a single toggle, for
|
|
21
|
+
* the pointer cursor (toggle-row.css). State lives in a WeakMap (no expando
|
|
22
|
+
* keys), so the row's own attributes and serialization stay untouched.
|
|
23
|
+
*/
|
|
24
|
+
import type { Directive } from "vue";
|
|
25
|
+
import "./toggle-row.css";
|
|
26
|
+
|
|
27
|
+
const TOGGLE = '[role="checkbox"], [role="switch"]';
|
|
28
|
+
/** Value controls that make "the row's control" ambiguous. */
|
|
29
|
+
const OTHER_CONTROL =
|
|
30
|
+
'input:not([type="hidden"]), select, textarea, [role="combobox"], [role="slider"], [role="spinbutton"], [role="radio"], [role="listbox"], [contenteditable=""], [contenteditable="true"]';
|
|
31
|
+
/** A click that lands on one of these belongs to it, not to the row. */
|
|
32
|
+
const INTERACTIVE =
|
|
33
|
+
'button, a[href], input, select, textarea, label, summary, [role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="radio"], [role="tab"], [role="menuitem"], [role="option"], [role="combobox"], [role="slider"], [role="spinbutton"], [contenteditable=""], [contenteditable="true"], .wp-checkbox';
|
|
34
|
+
|
|
35
|
+
interface State {
|
|
36
|
+
off: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const states = new WeakMap<HTMLElement, State>();
|
|
40
|
+
/** One click flips one toggle, even with rows nested in rows. */
|
|
41
|
+
const handled = new WeakSet<Event>();
|
|
42
|
+
|
|
43
|
+
/** The row's one toggle, or null when it has none, several, or another control beside it. */
|
|
44
|
+
export function rowToggle(row: HTMLElement): HTMLElement | null {
|
|
45
|
+
const toggles = row.querySelectorAll<HTMLElement>(TOGGLE);
|
|
46
|
+
if (toggles.length !== 1) return null;
|
|
47
|
+
if (row.querySelector(OTHER_CONTROL)) return null;
|
|
48
|
+
return toggles[0] ?? null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isDisabled(el: HTMLElement): boolean {
|
|
52
|
+
return el.getAttribute("aria-disabled") === "true" || (el instanceof HTMLButtonElement && el.disabled);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function sync(row: HTMLElement): void {
|
|
56
|
+
const toggle = rowToggle(row);
|
|
57
|
+
row.classList.toggle("wp-toggle-row", !!toggle);
|
|
58
|
+
row.classList.toggle("wp-toggle-row--disabled", !!toggle && isDisabled(toggle));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Flip the row's toggle for a click on the row's words. Exported for tests. */
|
|
62
|
+
export function onRowClick(row: HTMLElement, event: MouseEvent): void {
|
|
63
|
+
if (handled.has(event) || event.button !== 0) return;
|
|
64
|
+
const target = event.target;
|
|
65
|
+
if (!(target instanceof Element)) return;
|
|
66
|
+
const hit = target.closest(INTERACTIVE);
|
|
67
|
+
if (hit && hit !== row && row.contains(hit)) return;
|
|
68
|
+
// Text the reader was selecting is not a click on the label.
|
|
69
|
+
const selection = row.ownerDocument.getSelection?.();
|
|
70
|
+
if (selection && !selection.isCollapsed && row.contains(selection.anchorNode)) return;
|
|
71
|
+
const toggle = rowToggle(row);
|
|
72
|
+
if (!toggle || isDisabled(toggle)) return;
|
|
73
|
+
handled.add(event);
|
|
74
|
+
toggle.click();
|
|
75
|
+
toggle.focus({ preventScroll: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function attach(el: HTMLElement): void {
|
|
79
|
+
if (states.has(el)) return;
|
|
80
|
+
const click = (e: MouseEvent): void => onRowClick(el, e);
|
|
81
|
+
el.addEventListener("click", click);
|
|
82
|
+
states.set(el, { off: () => el.removeEventListener("click", click) });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function detach(el: HTMLElement): void {
|
|
86
|
+
states.get(el)?.off();
|
|
87
|
+
states.delete(el);
|
|
88
|
+
el.classList.remove("wp-toggle-row", "wp-toggle-row--disabled");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `v-toggle-row="false"` turns it off for one row (a row whose words do something else). */
|
|
92
|
+
export const vToggleRow: Directive<HTMLElement, boolean | undefined> = {
|
|
93
|
+
mounted(el, binding) {
|
|
94
|
+
if (binding.value === false) return;
|
|
95
|
+
attach(el);
|
|
96
|
+
sync(el);
|
|
97
|
+
},
|
|
98
|
+
updated(el, binding) {
|
|
99
|
+
if (binding.value === false) return detach(el);
|
|
100
|
+
attach(el);
|
|
101
|
+
sync(el);
|
|
102
|
+
},
|
|
103
|
+
unmounted: detach,
|
|
104
|
+
};
|