@abraca/nuxt 2.14.0 → 2.16.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/dist/module.json +1 -1
- package/dist/runtime/assets/editor.css +3 -1
- package/dist/runtime/components/ACodeEditor.vue +16 -2
- package/dist/runtime/components/ANodePanel.vue +7 -5
- package/dist/runtime/components/aware/ASlider.d.vue.ts +1 -1
- package/dist/runtime/components/aware/ASlider.vue.d.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +1 -1
- package/dist/runtime/components/editor/AColorPalettePopover.vue +97 -5
- package/dist/runtime/components/editor/AIconPickerPopover.vue +81 -3
- package/dist/runtime/components/editor/AMetaNumberStepper.d.vue.ts +40 -0
- package/dist/runtime/components/editor/AMetaNumberStepper.vue +214 -0
- package/dist/runtime/components/editor/AMetaNumberStepper.vue.d.ts +40 -0
- package/dist/runtime/components/registry/APluginBrowser.vue +18 -2
- package/dist/runtime/components/renderers/ACalendarRenderer.vue +7 -1
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
- package/dist/runtime/components/settings/APluginInstallDialog.d.vue.ts +39 -0
- package/dist/runtime/components/settings/APluginInstallDialog.vue +254 -0
- package/dist/runtime/components/settings/APluginInstallDialog.vue.d.ts +39 -0
- package/dist/runtime/components/settings/APluginsTabInstalled.d.vue.ts +7 -0
- package/dist/runtime/components/settings/APluginsTabInstalled.vue +413 -0
- package/dist/runtime/components/settings/APluginsTabInstalled.vue.d.ts +7 -0
- package/dist/runtime/components/settings/APluginsTabPending.d.vue.ts +24 -0
- package/dist/runtime/components/settings/APluginsTabPending.vue +248 -0
- package/dist/runtime/components/settings/APluginsTabPending.vue.d.ts +24 -0
- package/dist/runtime/components/settings/ASettingsPluginsPanel.d.vue.ts +14 -1
- package/dist/runtime/components/settings/ASettingsPluginsPanel.vue +34 -80
- package/dist/runtime/components/settings/ASettingsPluginsPanel.vue.d.ts +14 -1
- package/dist/runtime/composables/useDeclinedSpacePlugins.d.ts +7 -0
- package/dist/runtime/composables/useDeclinedSpacePlugins.js +24 -0
- package/dist/runtime/composables/useLoadTimePending.d.ts +29 -0
- package/dist/runtime/composables/useLoadTimePending.js +37 -0
- package/dist/runtime/composables/usePluginCatalog.d.ts +5 -1
- package/dist/runtime/composables/usePluginCatalog.js +34 -0
- package/dist/runtime/composables/useTouchDrag.d.ts +21 -4
- package/dist/runtime/composables/useTouchDrag.js +30 -0
- package/dist/runtime/composables/useUploadedPluginStore.d.ts +43 -0
- package/dist/runtime/composables/useUploadedPluginStore.js +66 -0
- package/dist/runtime/extensions/views/MetaFieldView.vue +17 -28
- package/dist/runtime/plugin-abracadabra.client.js +48 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
html
|
|
1
|
+
html .cm-editor,html .cm-editor .cm-content,html .cm-editor .cm-gutters,html .cm-editor .cm-scroller,html .cm-editor .cm-tooltip{font-family:var(
|
|
2
|
+
--font-code,ui-monospace,"SF Mono",Menlo,Monaco,Consolas,monospace
|
|
3
|
+
)!important}html.dark .tiptap .shiki,html.dark .tiptap .shiki span{background-color:var(--ui-bg-muted)!important;color:var(--shiki-dark)!important}.collaboration-carets__caret{border-left:1px solid #0d0d0d;border-right:1px solid #0d0d0d;margin-left:-1px;margin-right:-1px;opacity:0;pointer-events:none;position:relative;transition:opacity .3s ease;word-break:normal}.collaboration-carets__caret.is-hidden{display:none}.collaboration-carets__caret.is-active{opacity:1}.collaboration-carets__label{border-radius:3px 3px 3px 0;color:#0d0d0d;font-size:12px;font-style:normal;font-weight:600;left:-1px;line-height:normal;padding:.1rem .3rem;position:absolute;top:-1.4em;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.ProseMirror-yjs-selection,.collaboration-carets__selection{background-color:var(--collaboration-selection-color)!important;pointer-events:none}.search-highlight{background-color:color-mix(in srgb,var(--color-primary-400) 35%,transparent);border-radius:2px;padding:0 1px}.doc-passage-highlight{background-color:color-mix(in srgb,var(--color-success-400) 35%,transparent);border-radius:2px;padding:0 1px}.tiptap{min-height:100%;padding-bottom:8rem}.tiptap.file-drop-active{border-radius:4px;outline:2px dashed var(--ui-primary);outline-offset:4px}.tiptap .document-header{font-size:2.5rem;font-weight:800;letter-spacing:-.025em;line-height:1.2;margin-bottom:1rem}.tiptap [data-type=document-meta]{align-items:center;display:flex;flex-wrap:wrap;font-size:.875rem;gap:.375rem;line-height:1.75rem;margin-bottom:1.5rem;min-height:1.75rem}.tiptap [data-type=document-meta][data-cursor-in-meta=true][data-empty=true]:after{color:var(--ui-text-dimmed);content:"Type '/' to add a property…";pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.prose-variant .tiptap [data-type=document-meta]{display:none}
|
|
@@ -57,7 +57,10 @@ function buildTheme(bundle) {
|
|
|
57
57
|
".cm-content": {
|
|
58
58
|
fontFamily: 'var(--font-code, ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace) !important',
|
|
59
59
|
caretColor: "var(--ui-text-highlighted)",
|
|
60
|
-
padding
|
|
60
|
+
// Top padding leaves room for a remote caret's name badge on the FIRST
|
|
61
|
+
// line — y-codemirror positions it at `top: -1.4em`, so without headroom
|
|
62
|
+
// it renders above the content box and is clipped by the toolbar above.
|
|
63
|
+
padding: "1.6em 0 8px"
|
|
61
64
|
},
|
|
62
65
|
// Local caret — `drawSelection()` hides the native caret and draws this
|
|
63
66
|
// bar, so style it to read like the native caret used elsewhere in the
|
|
@@ -145,7 +148,18 @@ function buildTheme(bundle) {
|
|
|
145
148
|
// `user.color`); we restyle shape only. The caret already matches TipTap
|
|
146
149
|
// (1px border bars, -1px margins); we only fix the label badge + hide the
|
|
147
150
|
// caret dot.
|
|
148
|
-
".cm-ySelection": {
|
|
151
|
+
".cm-ySelection": {
|
|
152
|
+
borderRadius: "1px",
|
|
153
|
+
// Fallback selection fill. y-codemirror paints the band via an inline
|
|
154
|
+
// `background-color: <user.colorLight>`; a valid inline value wins by
|
|
155
|
+
// specificity and keeps the per-user colour. But peers that broadcast
|
|
156
|
+
// only `user.color` (older builds, native gpui/apertura/aperio clients)
|
|
157
|
+
// leave colorLight as y-codemirror's invalid `color + '33'` default,
|
|
158
|
+
// which the browser drops — making the band invisible while the caret
|
|
159
|
+
// (which uses `color` directly) still shows. This neutral tint then
|
|
160
|
+
// applies, so a remote selection is always visible.
|
|
161
|
+
backgroundColor: "color-mix(in srgb, var(--ui-primary) 30%, transparent)"
|
|
162
|
+
},
|
|
149
163
|
".cm-ySelectionCaretDot": { display: "none" },
|
|
150
164
|
".cm-ySelectionInfo": {
|
|
151
165
|
opacity: "1",
|
|
@@ -646,11 +646,13 @@ const addFieldMenuItems = computed(
|
|
|
646
646
|
|
|
647
647
|
<!-- Number -->
|
|
648
648
|
<template v-else-if="field.type === 'number'">
|
|
649
|
-
<
|
|
650
|
-
|
|
651
|
-
:
|
|
652
|
-
|
|
653
|
-
|
|
649
|
+
<AMetaNumberStepper
|
|
650
|
+
:model-value="getCustomFieldValue(field) != null ? Number(getCustomFieldValue(field)) : void 0"
|
|
651
|
+
:min="field.min"
|
|
652
|
+
:max="field.max"
|
|
653
|
+
:step="field.step ?? 1"
|
|
654
|
+
:unit="field.unit"
|
|
655
|
+
@update:model-value="setCustomFieldValue(field, $event)"
|
|
654
656
|
/>
|
|
655
657
|
</template>
|
|
656
658
|
|
|
@@ -11,8 +11,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
11
11
|
sync: boolean;
|
|
12
12
|
peers: boolean;
|
|
13
13
|
awareness: boolean;
|
|
14
|
-
max: number;
|
|
15
14
|
min: number;
|
|
15
|
+
max: number;
|
|
16
16
|
total: boolean;
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
18
|
declare const _default: typeof __VLS_export;
|
|
@@ -11,8 +11,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
11
11
|
sync: boolean;
|
|
12
12
|
peers: boolean;
|
|
13
13
|
awareness: boolean;
|
|
14
|
-
max: number;
|
|
15
14
|
min: number;
|
|
15
|
+
max: number;
|
|
16
16
|
total: boolean;
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
18
|
declare const _default: typeof __VLS_export;
|
|
@@ -237,9 +237,9 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<import
|
|
|
237
237
|
}>> & Readonly<{}>, {
|
|
238
238
|
color: any;
|
|
239
239
|
disabled: boolean;
|
|
240
|
+
block: boolean;
|
|
240
241
|
square: boolean;
|
|
241
242
|
viewTransition: boolean;
|
|
242
|
-
block: boolean;
|
|
243
243
|
loading: boolean;
|
|
244
244
|
collapsed: boolean;
|
|
245
245
|
tooltip: boolean | Record<string, any>;
|
|
@@ -237,9 +237,9 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<import
|
|
|
237
237
|
}>> & Readonly<{}>, {
|
|
238
238
|
color: any;
|
|
239
239
|
disabled: boolean;
|
|
240
|
+
block: boolean;
|
|
240
241
|
square: boolean;
|
|
241
242
|
viewTransition: boolean;
|
|
242
|
-
block: boolean;
|
|
243
243
|
loading: boolean;
|
|
244
244
|
collapsed: boolean;
|
|
245
245
|
tooltip: boolean | Record<string, any>;
|
|
@@ -1,24 +1,106 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, watch, nextTick } from "vue";
|
|
3
3
|
import { COLOR_PALETTES } from "../../utils/colorPalettes";
|
|
4
|
+
const COLS = 11;
|
|
4
5
|
const props = defineProps({
|
|
5
6
|
modelValue: { type: String, required: true },
|
|
6
7
|
open: { type: Boolean, required: true }
|
|
7
8
|
});
|
|
8
9
|
const emit = defineEmits(["update:modelValue", "update:open"]);
|
|
9
10
|
const search = ref("");
|
|
11
|
+
const highlighted = ref(-1);
|
|
12
|
+
const scrollContainer = ref(null);
|
|
10
13
|
const filteredPalettes = computed(() => {
|
|
11
14
|
if (!search.value) return COLOR_PALETTES;
|
|
12
15
|
const q = search.value.toLowerCase();
|
|
13
16
|
return COLOR_PALETTES.filter((p) => p.name.toLowerCase().includes(q));
|
|
14
17
|
});
|
|
18
|
+
const paletteGroups = computed(() => {
|
|
19
|
+
let start = 0;
|
|
20
|
+
return filteredPalettes.value.map((p) => {
|
|
21
|
+
const group = { name: p.name, shades: p.shades, start };
|
|
22
|
+
start += p.shades.length;
|
|
23
|
+
return group;
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
const flatShades = computed(
|
|
27
|
+
() => filteredPalettes.value.flatMap(
|
|
28
|
+
(p) => p.shades.map((s) => ({ hex: s.hex, key: `${p.name}-${s.shade}` }))
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
watch(search, () => {
|
|
32
|
+
highlighted.value = -1;
|
|
33
|
+
});
|
|
15
34
|
function selectHex(hex) {
|
|
16
35
|
emit("update:modelValue", hex);
|
|
17
36
|
emit("update:open", false);
|
|
18
37
|
}
|
|
38
|
+
function scrollHighlightedIntoView() {
|
|
39
|
+
nextTick(() => {
|
|
40
|
+
scrollContainer.value?.querySelector(`[data-idx="${highlighted.value}"]`)?.scrollIntoView({ block: "nearest" });
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function setHighlight(i) {
|
|
44
|
+
const max = flatShades.value.length - 1;
|
|
45
|
+
if (max < 0) return;
|
|
46
|
+
highlighted.value = Math.max(0, Math.min(max, i));
|
|
47
|
+
scrollHighlightedIntoView();
|
|
48
|
+
}
|
|
49
|
+
function onKeydown(e) {
|
|
50
|
+
switch (e.key) {
|
|
51
|
+
case "ArrowDown":
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
setHighlight(highlighted.value < 0 ? 0 : highlighted.value + COLS);
|
|
54
|
+
break;
|
|
55
|
+
case "ArrowUp":
|
|
56
|
+
if (highlighted.value < 0) return;
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
if (highlighted.value < COLS) {
|
|
59
|
+
highlighted.value = -1;
|
|
60
|
+
searchInput.value?.$el?.querySelector("input")?.focus();
|
|
61
|
+
} else {
|
|
62
|
+
setHighlight(highlighted.value - COLS);
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case "ArrowRight":
|
|
66
|
+
if (highlighted.value < 0) return;
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
setHighlight(highlighted.value + 1);
|
|
69
|
+
break;
|
|
70
|
+
case "ArrowLeft":
|
|
71
|
+
if (highlighted.value < 0) return;
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
setHighlight(highlighted.value - 1);
|
|
74
|
+
break;
|
|
75
|
+
case "Home":
|
|
76
|
+
if (highlighted.value < 0) return;
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
setHighlight(0);
|
|
79
|
+
break;
|
|
80
|
+
case "End":
|
|
81
|
+
if (highlighted.value < 0) return;
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
setHighlight(flatShades.value.length - 1);
|
|
84
|
+
break;
|
|
85
|
+
case "Enter": {
|
|
86
|
+
const idx = highlighted.value >= 0 ? highlighted.value : 0;
|
|
87
|
+
const shade = flatShades.value[idx];
|
|
88
|
+
if (shade) {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
selectHex(shade.hex);
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "Escape":
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
emit("update:open", false);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
19
100
|
const searchInput = ref(null);
|
|
20
101
|
watch(() => props.open, async (open) => {
|
|
21
102
|
if (open) {
|
|
103
|
+
highlighted.value = -1;
|
|
22
104
|
await nextTick();
|
|
23
105
|
searchInput.value?.$el?.querySelector("input")?.focus();
|
|
24
106
|
}
|
|
@@ -33,7 +115,10 @@ watch(() => props.open, async (open) => {
|
|
|
33
115
|
>
|
|
34
116
|
<slot />
|
|
35
117
|
<template #content>
|
|
36
|
-
<div
|
|
118
|
+
<div
|
|
119
|
+
class="w-80 p-2 flex flex-col gap-2"
|
|
120
|
+
@keydown="onKeydown"
|
|
121
|
+
>
|
|
37
122
|
<UInput
|
|
38
123
|
ref="searchInput"
|
|
39
124
|
v-model="search"
|
|
@@ -41,9 +126,12 @@ watch(() => props.open, async (open) => {
|
|
|
41
126
|
placeholder="Search colors..."
|
|
42
127
|
icon="i-lucide-search"
|
|
43
128
|
/>
|
|
44
|
-
<div
|
|
129
|
+
<div
|
|
130
|
+
ref="scrollContainer"
|
|
131
|
+
class="max-h-64 overflow-y-auto flex flex-col gap-1.5"
|
|
132
|
+
>
|
|
45
133
|
<div
|
|
46
|
-
v-for="palette in
|
|
134
|
+
v-for="palette in paletteGroups"
|
|
47
135
|
:key="palette.name"
|
|
48
136
|
>
|
|
49
137
|
<p class="text-xs text-(--ui-text-muted) mb-0.5 capitalize">
|
|
@@ -51,13 +139,17 @@ watch(() => props.open, async (open) => {
|
|
|
51
139
|
</p>
|
|
52
140
|
<div class="grid grid-cols-11 gap-0.5">
|
|
53
141
|
<UTooltip
|
|
54
|
-
v-for="shade in palette.shades"
|
|
142
|
+
v-for="(shade, shadeIdx) in palette.shades"
|
|
55
143
|
:key="shade.shade"
|
|
56
144
|
:text="`${palette.name}-${shade.shade}`"
|
|
57
145
|
>
|
|
58
146
|
<button
|
|
147
|
+
:data-idx="palette.start + shadeIdx"
|
|
59
148
|
class="size-5 rounded-sm cursor-pointer transition-transform hover:scale-110"
|
|
60
|
-
:class="
|
|
149
|
+
:class="[
|
|
150
|
+
modelValue === shade.hex ? 'ring-2 ring-offset-1 ring-(--ui-color-neutral-900) dark:ring-(--ui-color-neutral-100) scale-110' : '',
|
|
151
|
+
highlighted === palette.start + shadeIdx ? 'ring-2 ring-(--ui-primary) scale-110' : ''
|
|
152
|
+
]"
|
|
61
153
|
:style="{ background: shade.hex }"
|
|
62
154
|
@click="selectHex(shade.hex)"
|
|
63
155
|
/>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { ref, computed, watch, nextTick, onBeforeUnmount } from "vue";
|
|
3
3
|
import { LUCIDE_ICON_NAMES } from "../../utils/lucideIcons";
|
|
4
4
|
const BATCH_SIZE = 80;
|
|
5
|
+
const COLS = 8;
|
|
5
6
|
const props = defineProps({
|
|
6
7
|
modelValue: { type: String, required: true },
|
|
7
8
|
open: { type: Boolean, required: true },
|
|
@@ -12,6 +13,7 @@ const search = ref("");
|
|
|
12
13
|
const loadedCount = ref(BATCH_SIZE);
|
|
13
14
|
const sentinel = ref(null);
|
|
14
15
|
const scrollContainer = ref(null);
|
|
16
|
+
const highlighted = ref(-1);
|
|
15
17
|
const sourceIcons = computed(
|
|
16
18
|
() => props.options?.length ? props.options : LUCIDE_ICON_NAMES
|
|
17
19
|
);
|
|
@@ -26,6 +28,7 @@ const visibleIcons = computed(
|
|
|
26
28
|
const hasMore = computed(() => loadedCount.value < filteredIcons.value.length);
|
|
27
29
|
watch(search, () => {
|
|
28
30
|
loadedCount.value = BATCH_SIZE;
|
|
31
|
+
highlighted.value = -1;
|
|
29
32
|
scrollContainer.value?.scrollTo(0, 0);
|
|
30
33
|
});
|
|
31
34
|
let observer = null;
|
|
@@ -47,6 +50,7 @@ watch(() => props.open, async (open) => {
|
|
|
47
50
|
if (open) {
|
|
48
51
|
loadedCount.value = BATCH_SIZE;
|
|
49
52
|
search.value = "";
|
|
53
|
+
highlighted.value = -1;
|
|
50
54
|
await nextTick();
|
|
51
55
|
searchInput.value?.$el?.querySelector("input")?.focus();
|
|
52
56
|
await nextTick();
|
|
@@ -60,6 +64,73 @@ function selectIcon(name) {
|
|
|
60
64
|
emit("update:modelValue", name);
|
|
61
65
|
emit("update:open", false);
|
|
62
66
|
}
|
|
67
|
+
function scrollHighlightedIntoView() {
|
|
68
|
+
nextTick(() => {
|
|
69
|
+
scrollContainer.value?.querySelector(`[data-idx="${highlighted.value}"]`)?.scrollIntoView({ block: "nearest" });
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function setHighlight(i) {
|
|
73
|
+
const max = visibleIcons.value.length - 1;
|
|
74
|
+
if (max < 0) return;
|
|
75
|
+
if (i > max && hasMore.value) {
|
|
76
|
+
loadedCount.value += BATCH_SIZE;
|
|
77
|
+
nextTick(() => setHighlight(i));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
highlighted.value = Math.max(0, Math.min(max, i));
|
|
81
|
+
scrollHighlightedIntoView();
|
|
82
|
+
}
|
|
83
|
+
function onKeydown(e) {
|
|
84
|
+
switch (e.key) {
|
|
85
|
+
case "ArrowDown":
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
setHighlight(highlighted.value < 0 ? 0 : highlighted.value + COLS);
|
|
88
|
+
break;
|
|
89
|
+
case "ArrowUp":
|
|
90
|
+
if (highlighted.value < 0) return;
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
if (highlighted.value < COLS) {
|
|
93
|
+
highlighted.value = -1;
|
|
94
|
+
searchInput.value?.$el?.querySelector("input")?.focus();
|
|
95
|
+
} else {
|
|
96
|
+
setHighlight(highlighted.value - COLS);
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
case "ArrowRight":
|
|
100
|
+
if (highlighted.value < 0) return;
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
setHighlight(highlighted.value + 1);
|
|
103
|
+
break;
|
|
104
|
+
case "ArrowLeft":
|
|
105
|
+
if (highlighted.value < 0) return;
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
setHighlight(highlighted.value - 1);
|
|
108
|
+
break;
|
|
109
|
+
case "Home":
|
|
110
|
+
if (highlighted.value < 0) return;
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
setHighlight(0);
|
|
113
|
+
break;
|
|
114
|
+
case "End":
|
|
115
|
+
if (highlighted.value < 0) return;
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
setHighlight(visibleIcons.value.length - 1);
|
|
118
|
+
break;
|
|
119
|
+
case "Enter": {
|
|
120
|
+
const idx = highlighted.value >= 0 ? highlighted.value : 0;
|
|
121
|
+
const name = visibleIcons.value[idx];
|
|
122
|
+
if (name) {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
selectIcon(name);
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "Escape":
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
emit("update:open", false);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
63
134
|
const searchInput = ref(null);
|
|
64
135
|
</script>
|
|
65
136
|
|
|
@@ -71,7 +142,10 @@ const searchInput = ref(null);
|
|
|
71
142
|
>
|
|
72
143
|
<slot />
|
|
73
144
|
<template #content>
|
|
74
|
-
<div
|
|
145
|
+
<div
|
|
146
|
+
class="w-72 p-2 flex flex-col gap-2"
|
|
147
|
+
@keydown="onKeydown"
|
|
148
|
+
>
|
|
75
149
|
<UInput
|
|
76
150
|
ref="searchInput"
|
|
77
151
|
v-model="search"
|
|
@@ -85,13 +159,17 @@ const searchInput = ref(null);
|
|
|
85
159
|
>
|
|
86
160
|
<div class="grid grid-cols-8 gap-0.5">
|
|
87
161
|
<UTooltip
|
|
88
|
-
v-for="name in visibleIcons"
|
|
162
|
+
v-for="(name, idx) in visibleIcons"
|
|
89
163
|
:key="name"
|
|
90
164
|
:text="name"
|
|
91
165
|
>
|
|
92
166
|
<button
|
|
167
|
+
:data-idx="idx"
|
|
93
168
|
class="size-8 flex items-center justify-center rounded cursor-pointer hover:bg-(--ui-bg-elevated)"
|
|
94
|
-
:class="
|
|
169
|
+
:class="[
|
|
170
|
+
modelValue === name ? 'bg-(--ui-bg-accented)' : '',
|
|
171
|
+
highlighted === idx ? 'ring-2 ring-(--ui-primary)' : ''
|
|
172
|
+
]"
|
|
95
173
|
@click="selectIcon(name)"
|
|
96
174
|
>
|
|
97
175
|
<UIcon
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-flush number stepper. The whole pill IS the control:
|
|
3
|
+
* [ label ] − ‹value› + [ unit ]
|
|
4
|
+
* • − / + flush ghost buttons (accent glyphs), press-and-hold to repeat
|
|
5
|
+
* • centre value: type to edit, vertical drag (mouse + touch) to scrub
|
|
6
|
+
* • scroll wheel to step (Shift = ×10), arrow keys to step (Shift = ×10)
|
|
7
|
+
* • value flashes on programmatic change (step / drag / wheel) for feedback
|
|
8
|
+
*/
|
|
9
|
+
type __VLS_Props = {
|
|
10
|
+
modelValue?: number | null;
|
|
11
|
+
min?: number;
|
|
12
|
+
max?: number;
|
|
13
|
+
step?: number;
|
|
14
|
+
unit?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
};
|
|
17
|
+
declare var __VLS_1: {};
|
|
18
|
+
type __VLS_Slots = {} & {
|
|
19
|
+
label?: (props: typeof __VLS_1) => any;
|
|
20
|
+
};
|
|
21
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
22
|
+
"update:modelValue": (value: number | undefined) => any;
|
|
23
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
24
|
+
"onUpdate:modelValue"?: ((value: number | undefined) => any) | undefined;
|
|
25
|
+
}>, {
|
|
26
|
+
unit: string;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
modelValue: number | null;
|
|
29
|
+
min: number;
|
|
30
|
+
max: number;
|
|
31
|
+
step: number;
|
|
32
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
33
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
34
|
+
declare const _default: typeof __VLS_export;
|
|
35
|
+
export default _default;
|
|
36
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
37
|
+
new (): {
|
|
38
|
+
$slots: S;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, onBeforeUnmount } from "vue";
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
modelValue: { type: [Number, null], required: false, default: void 0 },
|
|
5
|
+
min: { type: Number, required: false, default: Number.NEGATIVE_INFINITY },
|
|
6
|
+
max: { type: Number, required: false, default: Number.POSITIVE_INFINITY },
|
|
7
|
+
step: { type: Number, required: false, default: 1 },
|
|
8
|
+
unit: { type: String, required: false, default: "" },
|
|
9
|
+
disabled: { type: Boolean, required: false, default: false }
|
|
10
|
+
});
|
|
11
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
12
|
+
const PX_PER_STEP = 8;
|
|
13
|
+
const valueEl = ref(null);
|
|
14
|
+
const focused = ref(false);
|
|
15
|
+
const dragging = ref(false);
|
|
16
|
+
const flash = ref(false);
|
|
17
|
+
const draft = ref("");
|
|
18
|
+
const current = computed(() => typeof props.modelValue === "number" && Number.isFinite(props.modelValue) ? props.modelValue : 0);
|
|
19
|
+
const atMin = computed(() => current.value <= props.min);
|
|
20
|
+
const atMax = computed(() => current.value >= props.max);
|
|
21
|
+
const displayValue = computed(
|
|
22
|
+
() => focused.value ? draft.value : typeof props.modelValue === "number" ? String(props.modelValue) : ""
|
|
23
|
+
);
|
|
24
|
+
function clamp(n) {
|
|
25
|
+
return Math.max(props.min, Math.min(props.max, n));
|
|
26
|
+
}
|
|
27
|
+
function pulse() {
|
|
28
|
+
flash.value = false;
|
|
29
|
+
requestAnimationFrame(() => {
|
|
30
|
+
flash.value = true;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function commit(n, withFlash = true) {
|
|
34
|
+
const next = clamp(n);
|
|
35
|
+
if (next === props.modelValue) return;
|
|
36
|
+
emit("update:modelValue", next);
|
|
37
|
+
if (withFlash) pulse();
|
|
38
|
+
}
|
|
39
|
+
function stepBy(dir, multiplier = 1) {
|
|
40
|
+
if (props.disabled) return;
|
|
41
|
+
commit(current.value + dir * props.step * multiplier);
|
|
42
|
+
}
|
|
43
|
+
let holdTimer = null;
|
|
44
|
+
let repeatTimer = null;
|
|
45
|
+
function clearHold() {
|
|
46
|
+
if (holdTimer) {
|
|
47
|
+
clearTimeout(holdTimer);
|
|
48
|
+
holdTimer = null;
|
|
49
|
+
}
|
|
50
|
+
if (repeatTimer) {
|
|
51
|
+
clearInterval(repeatTimer);
|
|
52
|
+
repeatTimer = null;
|
|
53
|
+
}
|
|
54
|
+
window.removeEventListener("pointerup", clearHold);
|
|
55
|
+
}
|
|
56
|
+
function onStepPress(dir) {
|
|
57
|
+
if (props.disabled) return;
|
|
58
|
+
stepBy(dir);
|
|
59
|
+
holdTimer = setTimeout(() => {
|
|
60
|
+
repeatTimer = setInterval(() => stepBy(dir), 60);
|
|
61
|
+
}, 400);
|
|
62
|
+
window.addEventListener("pointerup", clearHold, { once: true });
|
|
63
|
+
}
|
|
64
|
+
function onWheel(e) {
|
|
65
|
+
if (props.disabled) return;
|
|
66
|
+
const dir = -Math.sign(e.deltaY);
|
|
67
|
+
if (!dir) return;
|
|
68
|
+
stepBy(dir, e.shiftKey ? 10 : 1);
|
|
69
|
+
}
|
|
70
|
+
let dragStartY = 0;
|
|
71
|
+
let dragStartVal = 0;
|
|
72
|
+
let moved = false;
|
|
73
|
+
function onValuePointerDown(e) {
|
|
74
|
+
if (props.disabled) return;
|
|
75
|
+
dragStartY = e.clientY;
|
|
76
|
+
dragStartVal = current.value;
|
|
77
|
+
moved = false;
|
|
78
|
+
window.addEventListener("pointermove", onValuePointerMove);
|
|
79
|
+
window.addEventListener("pointerup", onValuePointerUp, { once: true });
|
|
80
|
+
}
|
|
81
|
+
function onValuePointerMove(e) {
|
|
82
|
+
const dy = dragStartY - e.clientY;
|
|
83
|
+
if (!moved && Math.abs(dy) < 4) return;
|
|
84
|
+
if (!moved) {
|
|
85
|
+
moved = true;
|
|
86
|
+
dragging.value = true;
|
|
87
|
+
valueEl.value?.blur();
|
|
88
|
+
}
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
const steps = Math.round(dy / PX_PER_STEP);
|
|
91
|
+
commit(dragStartVal + steps * props.step);
|
|
92
|
+
}
|
|
93
|
+
function onValuePointerUp() {
|
|
94
|
+
window.removeEventListener("pointermove", onValuePointerMove);
|
|
95
|
+
dragging.value = false;
|
|
96
|
+
if (!moved) {
|
|
97
|
+
valueEl.value?.focus();
|
|
98
|
+
valueEl.value?.select();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function onFocus() {
|
|
102
|
+
focused.value = true;
|
|
103
|
+
draft.value = typeof props.modelValue === "number" ? String(props.modelValue) : "";
|
|
104
|
+
}
|
|
105
|
+
function onInput(e) {
|
|
106
|
+
draft.value = e.target.value;
|
|
107
|
+
const trimmed = draft.value.trim();
|
|
108
|
+
if (trimmed === "") return;
|
|
109
|
+
const n = Number(trimmed);
|
|
110
|
+
if (Number.isFinite(n)) commit(n, false);
|
|
111
|
+
}
|
|
112
|
+
function onBlur() {
|
|
113
|
+
focused.value = false;
|
|
114
|
+
const trimmed = draft.value.trim();
|
|
115
|
+
if (trimmed === "") {
|
|
116
|
+
if (props.modelValue !== void 0) emit("update:modelValue", void 0);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const n = Number(trimmed);
|
|
120
|
+
if (Number.isFinite(n)) commit(n, false);
|
|
121
|
+
}
|
|
122
|
+
function onKeydown(e) {
|
|
123
|
+
if (e.key === "ArrowUp") {
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
stepBy(1, e.shiftKey ? 10 : 1);
|
|
126
|
+
} else if (e.key === "ArrowDown") {
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
stepBy(-1, e.shiftKey ? 10 : 1);
|
|
129
|
+
} else if (e.key === "Enter") {
|
|
130
|
+
e.preventDefault();
|
|
131
|
+
valueEl.value?.blur();
|
|
132
|
+
} else if (e.key === "Escape") {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
draft.value = typeof props.modelValue === "number" ? String(props.modelValue) : "";
|
|
135
|
+
valueEl.value?.blur();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
onBeforeUnmount(() => {
|
|
139
|
+
clearHold();
|
|
140
|
+
window.removeEventListener("pointermove", onValuePointerMove);
|
|
141
|
+
});
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<template>
|
|
145
|
+
<div
|
|
146
|
+
class="meta-number-stepper flex items-center h-7 rounded-md border border-(--ui-border) text-sm overflow-hidden select-none transition-shadow"
|
|
147
|
+
:class="{
|
|
148
|
+
'opacity-60 pointer-events-none': disabled,
|
|
149
|
+
'ring-1 ring-(--ui-primary) ring-inset': dragging || focused
|
|
150
|
+
}"
|
|
151
|
+
@mousedown.stop
|
|
152
|
+
@touchstart.stop
|
|
153
|
+
@wheel.prevent="onWheel"
|
|
154
|
+
>
|
|
155
|
+
<span
|
|
156
|
+
v-if="$slots.label"
|
|
157
|
+
class="pl-2.5 pr-1 shrink-0 flex items-center"
|
|
158
|
+
>
|
|
159
|
+
<slot name="label" />
|
|
160
|
+
</span>
|
|
161
|
+
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
tabindex="-1"
|
|
165
|
+
class="h-full px-2 flex items-center justify-center text-(--ui-primary) hover:bg-(--ui-bg-elevated) active:scale-90 transition-transform disabled:opacity-30 disabled:pointer-events-none cursor-pointer"
|
|
166
|
+
:disabled="disabled || atMin"
|
|
167
|
+
aria-label="Decrease"
|
|
168
|
+
@pointerdown.stop.prevent="onStepPress(-1)"
|
|
169
|
+
>
|
|
170
|
+
<UIcon
|
|
171
|
+
name="i-lucide-minus"
|
|
172
|
+
class="size-3.5"
|
|
173
|
+
/>
|
|
174
|
+
</button>
|
|
175
|
+
|
|
176
|
+
<input
|
|
177
|
+
ref="valueEl"
|
|
178
|
+
type="text"
|
|
179
|
+
inputmode="decimal"
|
|
180
|
+
class="meta-number-value flex-1 min-w-0 w-10 bg-transparent text-center outline-none tabular-nums cursor-ns-resize"
|
|
181
|
+
:class="{ 'meta-number-value--flash': flash }"
|
|
182
|
+
:value="displayValue"
|
|
183
|
+
:disabled="disabled"
|
|
184
|
+
@pointerdown="onValuePointerDown"
|
|
185
|
+
@focus="onFocus"
|
|
186
|
+
@blur="onBlur"
|
|
187
|
+
@input="onInput"
|
|
188
|
+
@keydown="onKeydown"
|
|
189
|
+
>
|
|
190
|
+
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
tabindex="-1"
|
|
194
|
+
class="h-full px-2 flex items-center justify-center text-(--ui-primary) hover:bg-(--ui-bg-elevated) active:scale-90 transition-transform disabled:opacity-30 disabled:pointer-events-none cursor-pointer"
|
|
195
|
+
:disabled="disabled || atMax"
|
|
196
|
+
aria-label="Increase"
|
|
197
|
+
@pointerdown.stop.prevent="onStepPress(1)"
|
|
198
|
+
>
|
|
199
|
+
<UIcon
|
|
200
|
+
name="i-lucide-plus"
|
|
201
|
+
class="size-3.5"
|
|
202
|
+
/>
|
|
203
|
+
</button>
|
|
204
|
+
|
|
205
|
+
<span
|
|
206
|
+
v-if="unit"
|
|
207
|
+
class="pr-2.5 pl-0.5 shrink-0 text-(--ui-text-muted) text-xs"
|
|
208
|
+
>{{ unit }}</span>
|
|
209
|
+
</div>
|
|
210
|
+
</template>
|
|
211
|
+
|
|
212
|
+
<style scoped>
|
|
213
|
+
.meta-number-value{touch-action:none}.meta-number-value::-webkit-inner-spin-button,.meta-number-value::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.meta-number-value--flash{animation:meta-number-flash .22s ease-out}@keyframes meta-number-flash{0%{color:var(--ui-primary);transform:scale(1.22)}to{color:inherit;transform:scale(1)}}
|
|
214
|
+
</style>
|