@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/useModalKeyShield.ts
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
import { onBeforeUnmount, watch, type Ref } from "vue";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Stop keystrokes inside one of our modals from reaching ComfyUI's global
|
|
5
|
-
* shortcuts.
|
|
6
|
-
*
|
|
7
|
-
* ComfyUI binds `useEventListener(window, 'keydown', keybindHandler)` and only
|
|
8
|
-
* bails for text-editing targets:
|
|
9
|
-
*
|
|
10
|
-
* const target = event.composedPath()[0]
|
|
11
|
-
* if (keyCombo.isReservedByTextInput &&
|
|
12
|
-
* (target.tagName === 'TEXTAREA' || target.tagName === 'INPUT' ||
|
|
13
|
-
* target.contentEditable === 'true' || ...)) return
|
|
14
|
-
*
|
|
15
|
-
* So a plain letter pressed while focus sits on a button or a div inside our
|
|
16
|
-
* modal is not "reserved by text input" and runs the global keybinding —
|
|
17
|
-
* pressing `r` in an edit modal fired Refresh Node Definitions and froze the
|
|
18
|
-
* page. Its other guard, which skips global keybindings while a dialog is
|
|
19
|
-
* open, keys off ComfyUI's OWN dialog stack, which our modals never enter.
|
|
20
|
-
*
|
|
21
|
-
* The listener is bound to the modal ROOT in bubble phase. Anything inside the
|
|
22
|
-
* modal has already had its turn by then — inner `@keydown` handlers, input
|
|
23
|
-
* defaults, IME — and the event simply stops before it can climb to window.
|
|
24
|
-
*
|
|
25
|
-
* That alone is not enough, because a key only traverses the overlay if it is
|
|
26
|
-
* TARGETED inside it. Open a modal and type without clicking in it first and
|
|
27
|
-
* `document.activeElement` is still `<body>`, so the event goes body → window
|
|
28
|
-
* and never passes the shield at all — which is how `a` kept reaching ComfyUI
|
|
29
|
-
* even with the root listener in place. Two additions close that:
|
|
30
|
-
*
|
|
31
|
-
* - focus the overlay when it opens, so keystrokes land inside the modal;
|
|
32
|
-
* - guard at the document for keys targeted OUTSIDE the open modal, since a
|
|
33
|
-
* modal owns the keyboard while it is up. Escape still passes, so the
|
|
34
|
-
* modal's own window-level close handler keeps working.
|
|
35
|
-
*
|
|
36
|
-
* Because of that, a modal's own key handling must live on the root too, not
|
|
37
|
-
* on `window`: a window listener sits ABOVE the shield and would never see
|
|
38
|
-
* keys pressed inside its own modal. `onKey` is where that logic goes.
|
|
39
|
-
*/
|
|
40
|
-
export interface ModalKeyShieldOptions {
|
|
41
|
-
/** Runs for every keydown inside the modal, before the event is stopped. */
|
|
42
|
-
onKey?: ((event: KeyboardEvent) => void) | undefined;
|
|
43
|
-
/**
|
|
44
|
-
* Let a key through to the rest of the page. Use sparingly — the point of
|
|
45
|
-
* the shield is that a modal owns the keyboard while it is open.
|
|
46
|
-
*/
|
|
47
|
-
passThrough?: ((event: KeyboardEvent) => boolean) | undefined;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Wire the shield to a root element that exists only while the modal is open. */
|
|
51
|
-
export function useModalKeyShield(
|
|
52
|
-
root: Ref<HTMLElement | null | undefined>,
|
|
53
|
-
options: ModalKeyShieldOptions = {},
|
|
54
|
-
): void {
|
|
55
|
-
let bound: HTMLElement | null = null;
|
|
56
|
-
|
|
57
|
-
const onKeydown = (event: KeyboardEvent): void => {
|
|
58
|
-
options.onKey?.(event);
|
|
59
|
-
if (options.passThrough?.(event)) return;
|
|
60
|
-
// stopPropagation only: the default action still happens, so typing,
|
|
61
|
-
// shortcuts inside inputs and IME composition all behave normally.
|
|
62
|
-
event.stopPropagation();
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// Keyup carries no handling of ours, but extensions bind shortcuts on it
|
|
66
|
-
// and a half-seen chord is worse than none.
|
|
67
|
-
const onKeyup = (event: KeyboardEvent): void => {
|
|
68
|
-
if (options.passThrough?.(event)) return;
|
|
69
|
-
event.stopPropagation();
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Keys aimed outside the open modal — the canvas, the body — never reach the
|
|
73
|
-
// overlay, so they are stopped here instead. Document bubble runs before
|
|
74
|
-
// window, where ComfyUI's keybindHandler lives.
|
|
75
|
-
const onOutside = (event: KeyboardEvent): void => {
|
|
76
|
-
if (!bound) return;
|
|
77
|
-
const target = event.target;
|
|
78
|
-
if (target instanceof Node && bound.contains(target)) return;
|
|
79
|
-
// Escape belongs to whoever wants to close something.
|
|
80
|
-
if (event.key === "Escape") return;
|
|
81
|
-
if (options.passThrough?.(event)) return;
|
|
82
|
-
event.stopPropagation();
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const detach = (): void => {
|
|
86
|
-
if (!bound) return;
|
|
87
|
-
bound.removeEventListener("keydown", onKeydown);
|
|
88
|
-
bound.removeEventListener("keyup", onKeyup);
|
|
89
|
-
document.removeEventListener("keydown", onOutside);
|
|
90
|
-
document.removeEventListener("keyup", onOutside);
|
|
91
|
-
bound = null;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
watch(
|
|
95
|
-
root,
|
|
96
|
-
(el) => {
|
|
97
|
-
detach();
|
|
98
|
-
if (!el) return;
|
|
99
|
-
bound = el;
|
|
100
|
-
el.addEventListener("keydown", onKeydown);
|
|
101
|
-
el.addEventListener("keyup", onKeyup);
|
|
102
|
-
document.addEventListener("keydown", onOutside);
|
|
103
|
-
document.addEventListener("keyup", onOutside);
|
|
104
|
-
// Pull focus in, so typing lands inside the modal rather than on <body>.
|
|
105
|
-
// Only when focus is not already somewhere inside — an autofocused input
|
|
106
|
-
// must keep it.
|
|
107
|
-
if (!el.contains(document.activeElement)) {
|
|
108
|
-
if (!el.hasAttribute("tabindex")) el.setAttribute("tabindex", "-1");
|
|
109
|
-
el.focus({ preventScroll: true });
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
{ immediate: true, flush: "post" },
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
onBeforeUnmount(detach);
|
|
116
|
-
}
|
|
1
|
+
import { onBeforeUnmount, watch, type Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stop keystrokes inside one of our modals from reaching ComfyUI's global
|
|
5
|
+
* shortcuts.
|
|
6
|
+
*
|
|
7
|
+
* ComfyUI binds `useEventListener(window, 'keydown', keybindHandler)` and only
|
|
8
|
+
* bails for text-editing targets:
|
|
9
|
+
*
|
|
10
|
+
* const target = event.composedPath()[0]
|
|
11
|
+
* if (keyCombo.isReservedByTextInput &&
|
|
12
|
+
* (target.tagName === 'TEXTAREA' || target.tagName === 'INPUT' ||
|
|
13
|
+
* target.contentEditable === 'true' || ...)) return
|
|
14
|
+
*
|
|
15
|
+
* So a plain letter pressed while focus sits on a button or a div inside our
|
|
16
|
+
* modal is not "reserved by text input" and runs the global keybinding —
|
|
17
|
+
* pressing `r` in an edit modal fired Refresh Node Definitions and froze the
|
|
18
|
+
* page. Its other guard, which skips global keybindings while a dialog is
|
|
19
|
+
* open, keys off ComfyUI's OWN dialog stack, which our modals never enter.
|
|
20
|
+
*
|
|
21
|
+
* The listener is bound to the modal ROOT in bubble phase. Anything inside the
|
|
22
|
+
* modal has already had its turn by then — inner `@keydown` handlers, input
|
|
23
|
+
* defaults, IME — and the event simply stops before it can climb to window.
|
|
24
|
+
*
|
|
25
|
+
* That alone is not enough, because a key only traverses the overlay if it is
|
|
26
|
+
* TARGETED inside it. Open a modal and type without clicking in it first and
|
|
27
|
+
* `document.activeElement` is still `<body>`, so the event goes body → window
|
|
28
|
+
* and never passes the shield at all — which is how `a` kept reaching ComfyUI
|
|
29
|
+
* even with the root listener in place. Two additions close that:
|
|
30
|
+
*
|
|
31
|
+
* - focus the overlay when it opens, so keystrokes land inside the modal;
|
|
32
|
+
* - guard at the document for keys targeted OUTSIDE the open modal, since a
|
|
33
|
+
* modal owns the keyboard while it is up. Escape still passes, so the
|
|
34
|
+
* modal's own window-level close handler keeps working.
|
|
35
|
+
*
|
|
36
|
+
* Because of that, a modal's own key handling must live on the root too, not
|
|
37
|
+
* on `window`: a window listener sits ABOVE the shield and would never see
|
|
38
|
+
* keys pressed inside its own modal. `onKey` is where that logic goes.
|
|
39
|
+
*/
|
|
40
|
+
export interface ModalKeyShieldOptions {
|
|
41
|
+
/** Runs for every keydown inside the modal, before the event is stopped. */
|
|
42
|
+
onKey?: ((event: KeyboardEvent) => void) | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Let a key through to the rest of the page. Use sparingly — the point of
|
|
45
|
+
* the shield is that a modal owns the keyboard while it is open.
|
|
46
|
+
*/
|
|
47
|
+
passThrough?: ((event: KeyboardEvent) => boolean) | undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Wire the shield to a root element that exists only while the modal is open. */
|
|
51
|
+
export function useModalKeyShield(
|
|
52
|
+
root: Ref<HTMLElement | null | undefined>,
|
|
53
|
+
options: ModalKeyShieldOptions = {},
|
|
54
|
+
): void {
|
|
55
|
+
let bound: HTMLElement | null = null;
|
|
56
|
+
|
|
57
|
+
const onKeydown = (event: KeyboardEvent): void => {
|
|
58
|
+
options.onKey?.(event);
|
|
59
|
+
if (options.passThrough?.(event)) return;
|
|
60
|
+
// stopPropagation only: the default action still happens, so typing,
|
|
61
|
+
// shortcuts inside inputs and IME composition all behave normally.
|
|
62
|
+
event.stopPropagation();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Keyup carries no handling of ours, but extensions bind shortcuts on it
|
|
66
|
+
// and a half-seen chord is worse than none.
|
|
67
|
+
const onKeyup = (event: KeyboardEvent): void => {
|
|
68
|
+
if (options.passThrough?.(event)) return;
|
|
69
|
+
event.stopPropagation();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Keys aimed outside the open modal — the canvas, the body — never reach the
|
|
73
|
+
// overlay, so they are stopped here instead. Document bubble runs before
|
|
74
|
+
// window, where ComfyUI's keybindHandler lives.
|
|
75
|
+
const onOutside = (event: KeyboardEvent): void => {
|
|
76
|
+
if (!bound) return;
|
|
77
|
+
const target = event.target;
|
|
78
|
+
if (target instanceof Node && bound.contains(target)) return;
|
|
79
|
+
// Escape belongs to whoever wants to close something.
|
|
80
|
+
if (event.key === "Escape") return;
|
|
81
|
+
if (options.passThrough?.(event)) return;
|
|
82
|
+
event.stopPropagation();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const detach = (): void => {
|
|
86
|
+
if (!bound) return;
|
|
87
|
+
bound.removeEventListener("keydown", onKeydown);
|
|
88
|
+
bound.removeEventListener("keyup", onKeyup);
|
|
89
|
+
document.removeEventListener("keydown", onOutside);
|
|
90
|
+
document.removeEventListener("keyup", onOutside);
|
|
91
|
+
bound = null;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
watch(
|
|
95
|
+
root,
|
|
96
|
+
(el) => {
|
|
97
|
+
detach();
|
|
98
|
+
if (!el) return;
|
|
99
|
+
bound = el;
|
|
100
|
+
el.addEventListener("keydown", onKeydown);
|
|
101
|
+
el.addEventListener("keyup", onKeyup);
|
|
102
|
+
document.addEventListener("keydown", onOutside);
|
|
103
|
+
document.addEventListener("keyup", onOutside);
|
|
104
|
+
// Pull focus in, so typing lands inside the modal rather than on <body>.
|
|
105
|
+
// Only when focus is not already somewhere inside — an autofocused input
|
|
106
|
+
// must keep it.
|
|
107
|
+
if (!el.contains(document.activeElement)) {
|
|
108
|
+
if (!el.hasAttribute("tabindex")) el.setAttribute("tabindex", "-1");
|
|
109
|
+
el.focus({ preventScroll: true });
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{ immediate: true, flush: "post" },
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
onBeforeUnmount(detach);
|
|
116
|
+
}
|
package/src/useToast.ts
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import { ref } from "vue";
|
|
2
|
-
|
|
3
|
-
export type ToastSeverity = "info" | "success" | "warn" | "error";
|
|
4
|
-
|
|
5
|
-
export interface ToastAction {
|
|
6
|
-
label: string;
|
|
7
|
-
run: () => void | Promise<void>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface ToastEntry {
|
|
11
|
-
id: string;
|
|
12
|
-
severity: ToastSeverity;
|
|
13
|
-
summary: string;
|
|
14
|
-
detail?: string;
|
|
15
|
-
life?: number;
|
|
16
|
-
/** Optional action button rendered inline with the toast. Click runs the handler
|
|
17
|
-
* and dismisses the toast. Reserved for Undo flows (Phase 3 bulk actions). */
|
|
18
|
-
action?: ToastAction;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const DEFAULT_LIFE = 2400;
|
|
22
|
-
|
|
23
|
-
const toasts = ref<ToastEntry[]>([]);
|
|
24
|
-
|
|
25
|
-
let nextId = 0;
|
|
26
|
-
function makeId() {
|
|
27
|
-
nextId += 1;
|
|
28
|
-
return `t${Date.now().toString(36)}_${nextId}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function dismiss(id: string) {
|
|
32
|
-
const i = toasts.value.findIndex((t) => t.id === id);
|
|
33
|
-
if (i >= 0) toasts.value.splice(i, 1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function push(entry: Omit<ToastEntry, "id">): string {
|
|
37
|
-
const id = makeId();
|
|
38
|
-
const life = entry.life ?? DEFAULT_LIFE;
|
|
39
|
-
toasts.value.push({ ...entry, id });
|
|
40
|
-
if (life > 0) {
|
|
41
|
-
setTimeout(() => dismiss(id), life);
|
|
42
|
-
}
|
|
43
|
-
return id;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function useToast() {
|
|
47
|
-
return { toasts, push, dismiss };
|
|
48
|
-
}
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export type ToastSeverity = "info" | "success" | "warn" | "error";
|
|
4
|
+
|
|
5
|
+
export interface ToastAction {
|
|
6
|
+
label: string;
|
|
7
|
+
run: () => void | Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ToastEntry {
|
|
11
|
+
id: string;
|
|
12
|
+
severity: ToastSeverity;
|
|
13
|
+
summary: string;
|
|
14
|
+
detail?: string;
|
|
15
|
+
life?: number;
|
|
16
|
+
/** Optional action button rendered inline with the toast. Click runs the handler
|
|
17
|
+
* and dismisses the toast. Reserved for Undo flows (Phase 3 bulk actions). */
|
|
18
|
+
action?: ToastAction;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DEFAULT_LIFE = 2400;
|
|
22
|
+
|
|
23
|
+
const toasts = ref<ToastEntry[]>([]);
|
|
24
|
+
|
|
25
|
+
let nextId = 0;
|
|
26
|
+
function makeId() {
|
|
27
|
+
nextId += 1;
|
|
28
|
+
return `t${Date.now().toString(36)}_${nextId}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function dismiss(id: string) {
|
|
32
|
+
const i = toasts.value.findIndex((t) => t.id === id);
|
|
33
|
+
if (i >= 0) toasts.value.splice(i, 1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function push(entry: Omit<ToastEntry, "id">): string {
|
|
37
|
+
const id = makeId();
|
|
38
|
+
const life = entry.life ?? DEFAULT_LIFE;
|
|
39
|
+
toasts.value.push({ ...entry, id });
|
|
40
|
+
if (life > 0) {
|
|
41
|
+
setTimeout(() => dismiss(id), life);
|
|
42
|
+
}
|
|
43
|
+
return id;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useToast() {
|
|
47
|
+
return { toasts, push, dismiss };
|
|
48
|
+
}
|
package/src/wp-logo.svg
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-name="Layer 1" viewBox="0 0 1024 1024" width="100%" height="100%"><defs>
|
|
2
|
-
<linearGradient id="wp-logo-grad-a" x1="312.56" y1="897.19" x2="312.56" y2="2" gradientUnits="userSpaceOnUse">
|
|
3
|
-
<stop offset="0" stop-color="#a970ff"></stop>
|
|
4
|
-
<stop offset="1" stop-color="#6366f1"></stop>
|
|
5
|
-
</linearGradient>
|
|
6
|
-
<linearGradient id="wp-logo-grad-b" x1="711.44" y1="126.81" x2="711.44" y2="1022" gradientUnits="userSpaceOnUse">
|
|
7
|
-
<stop offset="0" stop-color="#6366f1"></stop>
|
|
8
|
-
<stop offset="1" stop-color="#33d6c6"></stop>
|
|
9
|
-
</linearGradient>
|
|
10
|
-
</defs><path fill="url(#wp-logo-grad-a)" d="m512,2c-53.6,0-98.47,38.16-108.84,88.73-75.07,19.29-143.32,58.26-198.74,113.68-55.42,55.42-94.39,123.68-113.68,198.74-50.58,10.38-88.73,55.24-88.73,108.84s38.16,98.47,88.73,108.84c13.62,53.14,37.03,102.64,69.77,147.47,37.14,50.86,85.83,93.86,140.81,124.33,5.54,3.08,11.56,4.54,17.48,4.54,12.7,0,25.02-6.72,31.61-18.61,9.68-17.44,3.38-39.42-14.07-49.09-85.32-47.32-147.22-125.71-173.38-218.21,36.3-18.29,61.27-55.92,61.27-99.28s-24.94-80.97-61.23-99.27c34.22-119.96,129.79-215.52,249.74-249.74,18.3,36.29,55.92,61.23,99.27,61.23,61.27,0,111.11-49.84,111.11-111.11S573.27,2,512,2ZM113.11,473.11c21.44,0,38.89,17.44,38.89,38.89,0,20.1-15.32,36.69-34.91,38.69h-.01c-.18.02-.36.03-.53.04-1.13.1-2.28.16-3.43.16-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89Zm398.89-321.11c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Z"></path><path fill="url(#wp-logo-grad-b)" d="m933.27,403.16c-13.62-53.14-37.03-102.64-69.77-147.47-37.14-50.86-85.83-93.86-140.81-124.33-17.44-9.68-39.42-3.38-49.09,14.07-9.68,17.44-3.38,39.42,14.07,49.09,85.32,47.32,147.22,125.71,173.38,218.21-36.3,18.29-61.27,55.92-61.27,99.28s24.94,80.97,61.23,99.27c-34.22,119.96-129.79,215.52-249.74,249.74-18.3-36.29-55.92-61.23-99.27-61.23-61.27,0-111.11,49.84-111.11,111.11s49.84,111.11,111.11,111.11c53.6,0,98.47-38.16,108.84-88.73,75.07-19.29,143.32-58.26,198.74-113.68,55.42-55.42,94.39-123.68,113.68-198.74,50.58-10.38,88.73-55.24,88.73-108.84s-38.16-98.47-88.73-108.84Zm-421.27,546.62c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Zm398.89-398.89c-21.44,0-38.89-17.44-38.89-38.89,0-20.03,15.22-36.58,34.72-38.67.38-.04.77-.08,1.16-.11h.02c.99-.08,1.99-.11,2.99-.11,21.44,0,38.89,17.44,38.89,38.89s-17.44,38.89-38.89,38.89Z"></path><g>
|
|
11
|
-
<path fill="#a970ff" d="m735.02,517.56c-153.82,30.87-186.59,63.64-217.46,217.46-1.22,6.08-9.9,6.08-11.12,0-30.87-153.82-63.64-186.59-217.46-217.46-6.08-1.22-6.08-9.9,0-11.12,153.82-30.87,186.59-63.64,217.46-217.46,1.22-6.08,9.9-6.08,11.12,0,30.87,153.82,63.64,186.59,217.46,217.46,6.08,1.22,6.08,9.9,0,11.12Z"></path>
|
|
12
|
-
<path fill="#6366f1" d="m434.06,665.85c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z"></path>
|
|
13
|
-
<path fill="#33d6c6" d="m738.07,361.84c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z"></path>
|
|
14
|
-
</g></svg>
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-name="Layer 1" viewBox="0 0 1024 1024" width="100%" height="100%"><defs>
|
|
2
|
+
<linearGradient id="wp-logo-grad-a" x1="312.56" y1="897.19" x2="312.56" y2="2" gradientUnits="userSpaceOnUse">
|
|
3
|
+
<stop offset="0" stop-color="#a970ff"></stop>
|
|
4
|
+
<stop offset="1" stop-color="#6366f1"></stop>
|
|
5
|
+
</linearGradient>
|
|
6
|
+
<linearGradient id="wp-logo-grad-b" x1="711.44" y1="126.81" x2="711.44" y2="1022" gradientUnits="userSpaceOnUse">
|
|
7
|
+
<stop offset="0" stop-color="#6366f1"></stop>
|
|
8
|
+
<stop offset="1" stop-color="#33d6c6"></stop>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
</defs><path fill="url(#wp-logo-grad-a)" d="m512,2c-53.6,0-98.47,38.16-108.84,88.73-75.07,19.29-143.32,58.26-198.74,113.68-55.42,55.42-94.39,123.68-113.68,198.74-50.58,10.38-88.73,55.24-88.73,108.84s38.16,98.47,88.73,108.84c13.62,53.14,37.03,102.64,69.77,147.47,37.14,50.86,85.83,93.86,140.81,124.33,5.54,3.08,11.56,4.54,17.48,4.54,12.7,0,25.02-6.72,31.61-18.61,9.68-17.44,3.38-39.42-14.07-49.09-85.32-47.32-147.22-125.71-173.38-218.21,36.3-18.29,61.27-55.92,61.27-99.28s-24.94-80.97-61.23-99.27c34.22-119.96,129.79-215.52,249.74-249.74,18.3,36.29,55.92,61.23,99.27,61.23,61.27,0,111.11-49.84,111.11-111.11S573.27,2,512,2ZM113.11,473.11c21.44,0,38.89,17.44,38.89,38.89,0,20.1-15.32,36.69-34.91,38.69h-.01c-.18.02-.36.03-.53.04-1.13.1-2.28.16-3.43.16-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89Zm398.89-321.11c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Z"></path><path fill="url(#wp-logo-grad-b)" d="m933.27,403.16c-13.62-53.14-37.03-102.64-69.77-147.47-37.14-50.86-85.83-93.86-140.81-124.33-17.44-9.68-39.42-3.38-49.09,14.07-9.68,17.44-3.38,39.42,14.07,49.09,85.32,47.32,147.22,125.71,173.38,218.21-36.3,18.29-61.27,55.92-61.27,99.28s24.94,80.97,61.23,99.27c-34.22,119.96-129.79,215.52-249.74,249.74-18.3-36.29-55.92-61.23-99.27-61.23-61.27,0-111.11,49.84-111.11,111.11s49.84,111.11,111.11,111.11c53.6,0,98.47-38.16,108.84-88.73,75.07-19.29,143.32-58.26,198.74-113.68,55.42-55.42,94.39-123.68,113.68-198.74,50.58-10.38,88.73-55.24,88.73-108.84s-38.16-98.47-88.73-108.84Zm-421.27,546.62c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Zm398.89-398.89c-21.44,0-38.89-17.44-38.89-38.89,0-20.03,15.22-36.58,34.72-38.67.38-.04.77-.08,1.16-.11h.02c.99-.08,1.99-.11,2.99-.11,21.44,0,38.89,17.44,38.89,38.89s-17.44,38.89-38.89,38.89Z"></path><g>
|
|
11
|
+
<path fill="#a970ff" d="m735.02,517.56c-153.82,30.87-186.59,63.64-217.46,217.46-1.22,6.08-9.9,6.08-11.12,0-30.87-153.82-63.64-186.59-217.46-217.46-6.08-1.22-6.08-9.9,0-11.12,153.82-30.87,186.59-63.64,217.46-217.46,1.22-6.08,9.9-6.08,11.12,0,30.87,153.82,63.64,186.59,217.46,217.46,6.08,1.22,6.08,9.9,0,11.12Z"></path>
|
|
12
|
+
<path fill="#6366f1" d="m434.06,665.85c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z"></path>
|
|
13
|
+
<path fill="#33d6c6" d="m738.07,361.84c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z"></path>
|
|
14
|
+
</g></svg>
|
package/src/ChipToggle.vue
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* A pill you press to turn a filter or flag on and off: a tag filter, a
|
|
4
|
-
* module-type filter, "Recommended" on a version, a category you pick.
|
|
5
|
-
* `pressed` lights it in the accent, or in `color` (a kind colour, a
|
|
6
|
-
* category's colour) when given; with `color` the icon wears it even when
|
|
7
|
-
* off, so a row of type filters reads at a glance. `count` shows a small
|
|
8
|
-
* number after the label.
|
|
9
|
-
*
|
|
10
|
-
* <ChipToggle :pressed="tag === t.slug" icon="pi-tag" :count="t.usage" @click="pick(t)">{{ t.name }}</ChipToggle>
|
|
11
|
-
* <ChipToggle :pressed="kind === 'wildcard'" color="var(--wp-kind-wildcard)" icon="pi-sparkles">Wildcard</ChipToggle>
|
|
12
|
-
*
|
|
13
|
-
* A real `<button>` with `aria-pressed`; it reads only the shared tokens, so
|
|
14
|
-
* it works on a page and on the canvas.
|
|
15
|
-
*/
|
|
16
|
-
import { computed } from "vue";
|
|
17
|
-
import Icon from "./Icon.vue";
|
|
18
|
-
|
|
19
|
-
const props = withDefaults(defineProps<{
|
|
20
|
-
pressed?: boolean | undefined;
|
|
21
|
-
/** PrimeIcons name (`tag` or `pi-tag`) before the label. */
|
|
22
|
-
icon?: string | undefined;
|
|
23
|
-
/** Tint when pressed (and of the icon): any CSS colour. Default the accent. */
|
|
24
|
-
color?: string | null | undefined;
|
|
25
|
-
count?: number | string | undefined;
|
|
26
|
-
size?: "sm" | "md" | undefined;
|
|
27
|
-
disabled?: boolean | undefined;
|
|
28
|
-
}>(), { pressed: false, size: "sm" });
|
|
29
|
-
|
|
30
|
-
const style = computed(() => (props.color ? { "--chip-toggle-c": props.color } : undefined));
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
<template>
|
|
34
|
-
<button
|
|
35
|
-
type="button"
|
|
36
|
-
class="wp-chip-toggle"
|
|
37
|
-
:class="[`wp-chip-toggle--${size}`, { 'wp-chip-toggle--tinted': !!color }]"
|
|
38
|
-
:style="style"
|
|
39
|
-
:aria-pressed="pressed"
|
|
40
|
-
:disabled="disabled"
|
|
41
|
-
>
|
|
42
|
-
<Icon v-if="icon" class="wp-chip-toggle__icon" :name="icon" :size="11" />
|
|
43
|
-
<span class="wp-chip-toggle__label"><slot /></span>
|
|
44
|
-
<span v-if="count !== undefined" class="wp-chip-toggle__count">{{ count }}</span>
|
|
45
|
-
</button>
|
|
46
|
-
</template>
|
|
47
|
-
|
|
48
|
-
<style scoped>
|
|
49
|
-
.wp-chip-toggle {
|
|
50
|
-
--chip-toggle-c: var(--wp-accent-500);
|
|
51
|
-
display: inline-flex;
|
|
52
|
-
align-items: center;
|
|
53
|
-
gap: var(--wp-space-2);
|
|
54
|
-
height: var(--wp-control-h-sm);
|
|
55
|
-
padding: 0 var(--wp-space-4);
|
|
56
|
-
border: 1px solid var(--wp-border);
|
|
57
|
-
border-radius: var(--wp-radius-cap, 999px);
|
|
58
|
-
background: var(--wp-bg-3);
|
|
59
|
-
color: var(--wp-text-muted);
|
|
60
|
-
font: inherit;
|
|
61
|
-
font-family: var(--wp-font-sans);
|
|
62
|
-
font-size: var(--wp-text-sm);
|
|
63
|
-
font-weight: 500;
|
|
64
|
-
line-height: 1;
|
|
65
|
-
white-space: nowrap;
|
|
66
|
-
cursor: pointer;
|
|
67
|
-
transition: background var(--wp-motion-quick) ease, border-color var(--wp-motion-quick) ease, color var(--wp-motion-quick) ease;
|
|
68
|
-
}
|
|
69
|
-
.wp-chip-toggle--md { height: var(--wp-control-h-md); padding: 0 var(--wp-space-5); font-size: var(--wp-text-base); }
|
|
70
|
-
.wp-chip-toggle:hover:not(:disabled) { border-color: var(--wp-border-strong); color: var(--wp-text); }
|
|
71
|
-
.wp-chip-toggle:focus-visible { outline: none; box-shadow: var(--wp-focus-ring); }
|
|
72
|
-
.wp-chip-toggle:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
73
|
-
.wp-chip-toggle--tinted .wp-chip-toggle__icon { color: var(--chip-toggle-c); }
|
|
74
|
-
.wp-chip-toggle[aria-pressed="true"] {
|
|
75
|
-
border-color: color-mix(in oklab, var(--chip-toggle-c) 45%, transparent);
|
|
76
|
-
background: color-mix(in oklab, var(--chip-toggle-c) 16%, transparent);
|
|
77
|
-
color: var(--wp-accent-text-strong);
|
|
78
|
-
}
|
|
79
|
-
.wp-chip-toggle--tinted[aria-pressed="true"] { color: var(--chip-toggle-c); }
|
|
80
|
-
.wp-chip-toggle__count {
|
|
81
|
-
padding: 1px var(--wp-space-3);
|
|
82
|
-
border-radius: var(--wp-radius-cap, 999px);
|
|
83
|
-
background: var(--wp-bg-4, var(--wp-bg-2));
|
|
84
|
-
color: var(--wp-text-dim);
|
|
85
|
-
font-family: var(--wp-font-mono);
|
|
86
|
-
font-size: var(--wp-text-xs);
|
|
87
|
-
}
|
|
88
|
-
.wp-chip-toggle[aria-pressed="true"] .wp-chip-toggle__count {
|
|
89
|
-
background: color-mix(in oklab, var(--chip-toggle-c) 22%, transparent);
|
|
90
|
-
color: inherit;
|
|
91
|
-
}
|
|
92
|
-
</style>
|
package/src/KindChip.vue
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* The module-type chip: `wildcard`, `fixed_values`, `combine`, `constraint`,
|
|
4
|
-
* `derivation` or `bundle`, in the kind colour the extension uses for that
|
|
5
|
-
* type everywhere (`--wp-kind-*`; bundles are slate, `--wp-bundle-default`).
|
|
6
|
-
*
|
|
7
|
-
* <KindChip kind="module" subtype="wildcard" />
|
|
8
|
-
* <KindChip kind="bundle" />
|
|
9
|
-
*
|
|
10
|
-
* A module with no subtype reads as `fixed_values`, the oldest type.
|
|
11
|
-
*/
|
|
12
|
-
import { computed } from "vue";
|
|
13
|
-
|
|
14
|
-
export type KindChipSubtype = "fixed_values" | "combine" | "wildcard" | "constraint" | "derivation";
|
|
15
|
-
|
|
16
|
-
const props = defineProps<{
|
|
17
|
-
kind: "module" | "bundle";
|
|
18
|
-
/** A `"bundle"` subtype reads as a bundle too (some APIs report it so). */
|
|
19
|
-
subtype?: KindChipSubtype | "bundle" | null | undefined;
|
|
20
|
-
}>();
|
|
21
|
-
|
|
22
|
-
const type = computed(() => (props.kind === "bundle" ? "bundle" : (props.subtype ?? "fixed_values")));
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<template>
|
|
26
|
-
<span class="wp-kind-chip" :data-type="type">{{ type }}</span>
|
|
27
|
-
</template>
|
|
28
|
-
|
|
29
|
-
<style scoped>
|
|
30
|
-
.wp-kind-chip {
|
|
31
|
-
--kind-chip-c: var(--wp-text-muted);
|
|
32
|
-
display: inline-flex;
|
|
33
|
-
align-items: center;
|
|
34
|
-
gap: 5px;
|
|
35
|
-
padding: 3px 9px;
|
|
36
|
-
border-radius: var(--wp-radius-cap, 999px);
|
|
37
|
-
font-family: var(--wp-font-mono);
|
|
38
|
-
font-size: 11px;
|
|
39
|
-
font-weight: 500;
|
|
40
|
-
line-height: 14px;
|
|
41
|
-
letter-spacing: -0.01em;
|
|
42
|
-
white-space: nowrap;
|
|
43
|
-
color: var(--kind-chip-c);
|
|
44
|
-
background: color-mix(in oklab, var(--kind-chip-c) 14%, transparent);
|
|
45
|
-
border: 1px solid color-mix(in oklab, var(--kind-chip-c) 38%, transparent);
|
|
46
|
-
}
|
|
47
|
-
.wp-kind-chip[data-type="fixed_values"] { --kind-chip-c: var(--wp-kind-fixed); }
|
|
48
|
-
.wp-kind-chip[data-type="combine"] { --kind-chip-c: var(--wp-kind-combine); }
|
|
49
|
-
.wp-kind-chip[data-type="wildcard"] { --kind-chip-c: var(--wp-kind-wildcard); }
|
|
50
|
-
.wp-kind-chip[data-type="constraint"] { --kind-chip-c: var(--wp-kind-constraint); }
|
|
51
|
-
.wp-kind-chip[data-type="derivation"] { --kind-chip-c: var(--wp-kind-derivation); }
|
|
52
|
-
.wp-kind-chip[data-type="bundle"] { --kind-chip-c: var(--wp-bundle-default); }
|
|
53
|
-
</style>
|
package/src/RatingBadge.vue
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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>
|