@leavepulse/ui 0.42.0 → 0.43.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/assets/{LpCommandPalette-Esv0a-gS.css → LpCommandPalette-s_kKuCRf.css} +21 -21
- package/dist/chunks/{LpAddressInput-CMjaPQVw.js → LpAddressInput-B-Zvxsn7.js} +1 -1
- package/dist/chunks/{LpAppShell-Bcv6EhqU.js → LpAppShell-BGQVIY-3.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-BNAvOR24.js → LpConfirmDialog-Q-bxff8z.js} +1 -1
- package/dist/chunks/{LpDrawer-DgaS2s2i.js → LpDrawer-BZ6k3hvo.js} +3 -3
- package/dist/chunks/{LpLightbox-oLqyjOln.js → LpLightbox-2sYVMY-S.js} +12 -4
- package/dist/chunks/{LpMediaGallery-Cid7PGYx.js → LpMediaGallery-FF-NquIo.js} +1 -1
- package/dist/chunks/{LpModal-8bja5bWS.js → LpModal-Bt0pQCI_.js} +2 -2
- package/dist/chunks/{LpModalNav-w9kfwHqO.js → LpModalNav-YaifHrXC.js} +2 -2
- package/dist/chunks/{LpSidebar-DlTn5nXc.js → LpSidebar-ClBUwoEz.js} +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +19 -72
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/commandSearch.js +66 -0
- package/dist/components/modalDepth.js +3 -2
- package/dist/index.js +13 -9
- package/package.json +3 -3
- package/src/components/LpCommandPalette.vue +18 -110
- package/src/components/LpDrawer.vue +3 -3
- package/src/components/LpLightbox.vue +11 -5
- package/src/components/LpModal.vue +2 -2
- package/src/components/commandSearch.ts +92 -0
- package/src/components/modalDepth.ts +4 -48
- package/src/index.ts +2 -0
- package/src/tokens/tokens.css +13 -19
- package/dist/component-names.d.ts +0 -1
- package/dist/components/LayoutCanvas.vue.d.ts +0 -28
- package/dist/components/LayoutNode.vue.d.ts +0 -40
- package/dist/components/LpActionBar.vue.d.ts +0 -84
- package/dist/components/LpAddressInput.vue.d.ts +0 -70
- package/dist/components/LpAlert.vue.d.ts +0 -44
- package/dist/components/LpAppShell.vue.d.ts +0 -123
- package/dist/components/LpAutocomplete.vue.d.ts +0 -50
- package/dist/components/LpAvatar.vue.d.ts +0 -34
- package/dist/components/LpBadge.vue.d.ts +0 -20
- package/dist/components/LpBreadcrumbs.vue.d.ts +0 -32
- package/dist/components/LpButton.vue.d.ts +0 -113
- package/dist/components/LpCalendar.vue.d.ts +0 -29
- package/dist/components/LpCard.vue.d.ts +0 -60
- package/dist/components/LpChart.vue.d.ts +0 -130
- package/dist/components/LpCheckbox.vue.d.ts +0 -32
- package/dist/components/LpCodeBlock.vue.d.ts +0 -58
- package/dist/components/LpColorPicker.vue.d.ts +0 -124
- package/dist/components/LpCommandPalette.vue.d.ts +0 -206
- package/dist/components/LpConfirmDialog.vue.d.ts +0 -59
- package/dist/components/LpContextMenu.vue.d.ts +0 -44
- package/dist/components/LpDatePicker.vue.d.ts +0 -33
- package/dist/components/LpDisclosure.vue.d.ts +0 -47
- package/dist/components/LpDivider.vue.d.ts +0 -17
- package/dist/components/LpDrawer.vue.d.ts +0 -170
- package/dist/components/LpDropdownMenu.vue.d.ts +0 -23
- package/dist/components/LpDropzone.vue.d.ts +0 -90
- package/dist/components/LpEmptyState.vue.d.ts +0 -28
- package/dist/components/LpFileTree.vue.d.ts +0 -126
- package/dist/components/LpFileTreeNode.vue.d.ts +0 -62
- package/dist/components/LpFormField.vue.d.ts +0 -24
- package/dist/components/LpIcon.vue.d.ts +0 -8
- package/dist/components/LpImageEditor.vue.d.ts +0 -106
- package/dist/components/LpInfraNode.vue.d.ts +0 -65
- package/dist/components/LpInput.vue.d.ts +0 -80
- package/dist/components/LpLaneNode.vue.d.ts +0 -24
- package/dist/components/LpLightbox.vue.d.ts +0 -62
- package/dist/components/LpLink.vue.d.ts +0 -93
- package/dist/components/LpLogViewer.vue.d.ts +0 -153
- package/dist/components/LpMapPicker.vue.d.ts +0 -59
- package/dist/components/LpMediaGallery.vue.d.ts +0 -80
- package/dist/components/LpModal.vue.d.ts +0 -66
- package/dist/components/LpModalNav.vue.d.ts +0 -66
- package/dist/components/LpNotificationBell.vue.d.ts +0 -85
- package/dist/components/LpNumberField.vue.d.ts +0 -21
- package/dist/components/LpNumberFlow.vue.d.ts +0 -35
- package/dist/components/LpOtpInput.vue.d.ts +0 -39
- package/dist/components/LpPagination.vue.d.ts +0 -35
- package/dist/components/LpPasswordInput.vue.d.ts +0 -36
- package/dist/components/LpPhoneInput.vue.d.ts +0 -49
- package/dist/components/LpPie.vue.d.ts +0 -196
- package/dist/components/LpPopover.vue.d.ts +0 -34
- package/dist/components/LpProgress.vue.d.ts +0 -66
- package/dist/components/LpRadio.vue.d.ts +0 -18
- package/dist/components/LpRadioGroup.vue.d.ts +0 -26
- package/dist/components/LpRollingText.vue.d.ts +0 -33
- package/dist/components/LpScrollArea.vue.d.ts +0 -28
- package/dist/components/LpSection.vue.d.ts +0 -120
- package/dist/components/LpSegmented.vue.d.ts +0 -30
- package/dist/components/LpSelect.vue.d.ts +0 -47
- package/dist/components/LpServiceNode.vue.d.ts +0 -14
- package/dist/components/LpShift.vue.d.ts +0 -36
- package/dist/components/LpShortcutsDialog.vue.d.ts +0 -24
- package/dist/components/LpSidebar.vue.d.ts +0 -146
- package/dist/components/LpSidebarNav.vue.d.ts +0 -54
- package/dist/components/LpSkeleton.vue.d.ts +0 -16
- package/dist/components/LpSlider.vue.d.ts +0 -30
- package/dist/components/LpSparkline.vue.d.ts +0 -48
- package/dist/components/LpStat.vue.d.ts +0 -69
- package/dist/components/LpStepper.vue.d.ts +0 -36
- package/dist/components/LpSwitch.vue.d.ts +0 -42
- package/dist/components/LpTable.vue.d.ts +0 -124
- package/dist/components/LpTableOfContents.vue.d.ts +0 -19
- package/dist/components/LpTabs.vue.d.ts +0 -47
- package/dist/components/LpTextarea.vue.d.ts +0 -22
- package/dist/components/LpThemeSwitcher.vue.d.ts +0 -47
- package/dist/components/LpTilt.vue.d.ts +0 -31
- package/dist/components/LpTimeField.vue.d.ts +0 -24
- package/dist/components/LpToaster.vue.d.ts +0 -3
- package/dist/components/LpTooltip.vue.d.ts +0 -58
- package/dist/components/LpTopologyCanvas.vue.d.ts +0 -205
- package/dist/components/LpUptimeBar.vue.d.ts +0 -46
- package/dist/components/LpWaypointEdge.vue.d.ts +0 -21
- package/dist/components/actionBar.d.ts +0 -19
- package/dist/components/address.d.ts +0 -107
- package/dist/components/chart.d.ts +0 -180
- package/dist/components/codeHighlight.d.ts +0 -8
- package/dist/components/color.d.ts +0 -65
- package/dist/components/countries-data.d.ts +0 -2
- package/dist/components/countries.d.ts +0 -20
- package/dist/components/dropdown.d.ts +0 -29
- package/dist/components/fileTree.d.ts +0 -79
- package/dist/components/lightbox.d.ts +0 -24
- package/dist/components/modalDepth.d.ts +0 -15
- package/dist/components/sidebar.d.ts +0 -42
- package/dist/composables/easing.d.ts +0 -46
- package/dist/composables/useAsyncTask.d.ts +0 -18
- package/dist/composables/useClipboard.d.ts +0 -46
- package/dist/composables/useFormDraft.d.ts +0 -67
- package/dist/composables/useGlass.d.ts +0 -9
- package/dist/composables/useHotkeys.d.ts +0 -101
- package/dist/composables/useInputFilter.d.ts +0 -14
- package/dist/composables/useMergedClass.d.ts +0 -42
- package/dist/composables/usePillTransition.d.ts +0 -21
- package/dist/composables/useReveal.d.ts +0 -26
- package/dist/composables/useRovingFocus.d.ts +0 -49
- package/dist/composables/useSettled.d.ts +0 -26
- package/dist/composables/useShift.d.ts +0 -35
- package/dist/composables/useTilt.d.ts +0 -23
- package/dist/composables/useToast.d.ts +0 -41
- package/dist/composables/useZoomPan.d.ts +0 -58
- package/dist/icons.d.ts +0 -1
- package/dist/index.d.ts +0 -160
- package/dist/layout/registry.d.ts +0 -26
- package/dist/layout/tree.d.ts +0 -75
- package/dist/layout/useLayout.d.ts +0 -39
- package/dist/theme/backdrop.d.ts +0 -9
- package/dist/theme/config.d.ts +0 -16
- package/dist/theme/define.d.ts +0 -12
- package/dist/theme/presets.d.ts +0 -20
- package/dist/theme/tokens.d.ts +0 -75
- package/dist/theme/useTheme.d.ts +0 -46
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { _ } from "./chunks/LayoutCanvas-Dd20boUG.js";
|
|
2
|
-
import { _ as _2 } from "./chunks/LpAddressInput-
|
|
2
|
+
import { _ as _2 } from "./chunks/LpAddressInput-B-Zvxsn7.js";
|
|
3
3
|
import { _ as _3 } from "./chunks/LpMapPicker-ul4oYno6.js";
|
|
4
4
|
import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
|
|
5
5
|
import { _ as _4 } from "./chunks/LpActionBar-BlvtGB2P.js";
|
|
6
6
|
import { _ as _5 } from "./chunks/LpAlert-BAYHDSS1.js";
|
|
7
|
-
import { _ as _6 } from "./chunks/LpAppShell-
|
|
7
|
+
import { _ as _6 } from "./chunks/LpAppShell-BGQVIY-3.js";
|
|
8
8
|
import { _ as _7 } from "./chunks/LpAutocomplete-DExlIZe_.js";
|
|
9
9
|
import { _ as _8 } from "./chunks/LpAvatar-B3-xAOH3.js";
|
|
10
10
|
import { _ as _9 } from "./chunks/LpBadge-Fe0D1bYx.js";
|
|
@@ -19,12 +19,13 @@ import { _ as _15 } from "./chunks/LpCodeBlock-Bo0B4TFR.js";
|
|
|
19
19
|
import { _ as _16 } from "./chunks/LpColorPicker-BlXea7LM.js";
|
|
20
20
|
import { DEFAULT_SWATCHES, clampHsv, formatColor, hsvToHsl, hsvToRgb, isLight, normalizeHue, opaqueCss, parseColor, parseHex, rgbToHex, rgbToHsv } from "./components/color.js";
|
|
21
21
|
import { default as default3 } from "./components/LpCommandPalette.vue.js";
|
|
22
|
-
import {
|
|
22
|
+
import { parseCommandQuery, rankCommandItems, swapCommandTerms } from "./components/commandSearch.js";
|
|
23
|
+
import { _ as _17 } from "./chunks/LpConfirmDialog-Q-bxff8z.js";
|
|
23
24
|
import { _ as _18 } from "./chunks/LpContextMenu-CwW1QXe_.js";
|
|
24
25
|
import { _ as _19 } from "./chunks/LpDatePicker-CwOToxGq.js";
|
|
25
26
|
import { _ as _20 } from "./chunks/LpDisclosure-ChQYmjlR.js";
|
|
26
27
|
import { _ as _21 } from "./chunks/LpDivider-CUX46KR0.js";
|
|
27
|
-
import { _ as _22 } from "./chunks/LpDrawer-
|
|
28
|
+
import { _ as _22 } from "./chunks/LpDrawer-BZ6k3hvo.js";
|
|
28
29
|
import { _ as _23 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
|
|
29
30
|
import { default as default4 } from "./components/LpDropzone.vue.js";
|
|
30
31
|
import { _ as _24 } from "./chunks/LpEmptyState-Dsdqz1Wj.js";
|
|
@@ -35,13 +36,13 @@ import { _ as _27 } from "./chunks/LpIcon-CCnX5_2j.js";
|
|
|
35
36
|
import { default as default5 } from "./components/LpImageEditor.vue.js";
|
|
36
37
|
import { _ as _28 } from "./chunks/LpInfraNode-Ds_8SZgN.js";
|
|
37
38
|
import { _ as _29 } from "./chunks/LpInput-9EXWUM9B.js";
|
|
38
|
-
import { _ as _30 } from "./chunks/LpLightbox-
|
|
39
|
+
import { _ as _30 } from "./chunks/LpLightbox-2sYVMY-S.js";
|
|
39
40
|
import { fileNameOf } from "./components/lightbox.js";
|
|
40
41
|
import { _ as _31 } from "./chunks/LpLink-DCND-zyu.js";
|
|
41
42
|
import { _ as _32 } from "./chunks/LpLogViewer-CaRy5MeS.js";
|
|
42
|
-
import { _ as _33 } from "./chunks/LpMediaGallery-
|
|
43
|
-
import { _ as _34 } from "./chunks/LpModal-
|
|
44
|
-
import { _ as _35 } from "./chunks/LpModalNav-
|
|
43
|
+
import { _ as _33 } from "./chunks/LpMediaGallery-FF-NquIo.js";
|
|
44
|
+
import { _ as _34 } from "./chunks/LpModal-Bt0pQCI_.js";
|
|
45
|
+
import { _ as _35 } from "./chunks/LpModalNav-YaifHrXC.js";
|
|
45
46
|
import { _ as _36 } from "./chunks/LpNotificationBell-DyGGsMAM.js";
|
|
46
47
|
import { _ as _37 } from "./chunks/LpNumberField-CWuX08iX.js";
|
|
47
48
|
import { _ as _38 } from "./chunks/LpTimeField-K6nAGboX.js";
|
|
@@ -64,7 +65,7 @@ import { _ as _53 } from "./chunks/LpSelect-ANIVtrpM.js";
|
|
|
64
65
|
import { _ as _54 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
|
|
65
66
|
import { _ as _55 } from "./chunks/LpShift-DXNsauQZ.js";
|
|
66
67
|
import { default as default6 } from "./components/LpShortcutsDialog.vue.js";
|
|
67
|
-
import { _ as _56 } from "./chunks/LpSidebar-
|
|
68
|
+
import { _ as _56 } from "./chunks/LpSidebar-ClBUwoEz.js";
|
|
68
69
|
import { _ as _57 } from "./chunks/LpSkeleton-CTI-vBgW.js";
|
|
69
70
|
import { _ as _58 } from "./chunks/LpSlider-BwNIBkAK.js";
|
|
70
71
|
import { _ as _59 } from "./chunks/LpSparkline-C6xQ2dwv.js";
|
|
@@ -270,6 +271,7 @@ export {
|
|
|
270
271
|
normalizeHue,
|
|
271
272
|
opaqueCss,
|
|
272
273
|
parseColor,
|
|
274
|
+
parseCommandQuery,
|
|
273
275
|
parseConfig,
|
|
274
276
|
parseHex,
|
|
275
277
|
parseTheme,
|
|
@@ -277,6 +279,7 @@ export {
|
|
|
277
279
|
presets,
|
|
278
280
|
project,
|
|
279
281
|
provideGlass,
|
|
282
|
+
rankCommandItems,
|
|
280
283
|
removeBlock,
|
|
281
284
|
removeLeaf,
|
|
282
285
|
reorderTab,
|
|
@@ -292,6 +295,7 @@ export {
|
|
|
292
295
|
sortNodes,
|
|
293
296
|
subtreeIds,
|
|
294
297
|
subtreeSize,
|
|
298
|
+
swapCommandTerms,
|
|
295
299
|
t as themeFromCookie,
|
|
296
300
|
k as themeToCssRule,
|
|
297
301
|
m as themeToCssVars,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leavepulse/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LeavePulse token-driven Vue component kit.",
|
|
6
6
|
"repository": {
|
|
@@ -45,10 +45,11 @@
|
|
|
45
45
|
"scripts": {
|
|
46
46
|
"dev": "vite",
|
|
47
47
|
"gen:names": "node scripts/gen-component-names.mjs",
|
|
48
|
-
"build": "node scripts/gen-component-names.mjs && vue-tsc --noEmit -p tsconfig.build.json && vite build --config vite.lib.config.ts",
|
|
48
|
+
"build": "node scripts/gen-component-names.mjs && vue-tsc --noEmit -p tsconfig.build.json && vite build --config vite.lib.config.ts && vue-tsc --declaration --emitDeclarationOnly --noEmit false -p tsconfig.build.json --outDir dist",
|
|
49
49
|
"dev:dist": "vite build --watch --config vite.lib.config.ts",
|
|
50
50
|
"prepublishOnly": "node scripts/gen-component-names.mjs && vue-tsc --noEmit -p tsconfig.build.json && vite build --config vite.lib.config.ts",
|
|
51
51
|
"typecheck": "vue-tsc --noEmit",
|
|
52
|
+
"test": "bun test",
|
|
52
53
|
"release": "npm version patch -m \"chore: release v%s\" && git push --follow-tags",
|
|
53
54
|
"release:minor": "npm version minor -m \"chore: release v%s\" && git push --follow-tags",
|
|
54
55
|
"release:major": "npm version major -m \"chore: release v%s\" && git push --follow-tags"
|
|
@@ -83,7 +84,6 @@
|
|
|
83
84
|
"tailwindcss": "^4.2.4",
|
|
84
85
|
"typescript": "^5.6.3",
|
|
85
86
|
"vite": "^7.0.0",
|
|
86
|
-
"vite-plugin-dts": "^5.0.2",
|
|
87
87
|
"vite-plugin-lib-inject-css": "^2.2.2",
|
|
88
88
|
"vue": "3.5.31",
|
|
89
89
|
"vue-tsc": "^3.0.0"
|
|
@@ -36,13 +36,15 @@ import {
|
|
|
36
36
|
DialogRoot,
|
|
37
37
|
DialogTitle,
|
|
38
38
|
} from "reka-ui"
|
|
39
|
-
import { computed, nextTick, ref, shallowRef, useId, watch } from "vue"
|
|
39
|
+
import { computed, nextTick, onBeforeUnmount, ref, shallowRef, useId, watch } from "vue"
|
|
40
40
|
import LpIcon from "./LpIcon.vue"
|
|
41
41
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
42
42
|
import { useAsyncTask } from "../composables/useAsyncTask"
|
|
43
43
|
import { useGlass } from "../composables/useGlass"
|
|
44
44
|
import { useClipboard } from "../composables/useClipboard"
|
|
45
45
|
import { useHotkeys } from "../composables/useHotkeys"
|
|
46
|
+
import { useModalLayer } from "./modalDepth"
|
|
47
|
+
import { parseCommandQuery, rankCommandItems, swapCommandTerms } from "./commandSearch"
|
|
46
48
|
|
|
47
49
|
export interface Command {
|
|
48
50
|
id: string
|
|
@@ -234,6 +236,7 @@ const props = withDefaults(
|
|
|
234
236
|
)
|
|
235
237
|
|
|
236
238
|
const frosted = useGlass(() => props.glass)
|
|
239
|
+
const { claim, release, layer } = useModalLayer()
|
|
237
240
|
|
|
238
241
|
const DEFAULT_LABELS: PaletteLabels = {
|
|
239
242
|
navigate: "navigate",
|
|
@@ -453,123 +456,22 @@ const source = computed<Command[]>(() => (mode.value ? modeItems.value : props.c
|
|
|
453
456
|
* the one bilingual setup common enough to be worth a table — and it is a
|
|
454
457
|
* fallback, tried only when the query as typed matches nothing.
|
|
455
458
|
*/
|
|
456
|
-
const LATIN = "qwertyuiop[]asdfghjkl;'zxcvbnm,.`"
|
|
457
|
-
const CYRILLIC = "йцукенгшщзхъфывапролджэячсмитьбюё"
|
|
458
|
-
const SWAP = new Map<string, string>()
|
|
459
|
-
for (let i = 0; i < LATIN.length; i++) {
|
|
460
|
-
SWAP.set(LATIN[i]!, CYRILLIC[i]!)
|
|
461
|
-
SWAP.set(CYRILLIC[i]!, LATIN[i]!)
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function swapLayout(term: string): string {
|
|
465
|
-
return [...term].map((ch) => SWAP.get(ch) ?? ch).join("")
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/** Damerau-Levenshtein distance, giving up past 1: all a typo check needs. */
|
|
469
|
-
function withinOne(a: string, b: string): boolean {
|
|
470
|
-
if (Math.abs(a.length - b.length) > 1) return false
|
|
471
|
-
let i = 0
|
|
472
|
-
while (i < a.length && i < b.length && a[i] === b[i]) i++
|
|
473
|
-
if (i === a.length || i === b.length) return true
|
|
474
|
-
const rest = (x: number, y: number) => a.slice(i + x) === b.slice(i + y)
|
|
475
|
-
return rest(1, 1) || rest(1, 0) || rest(0, 1) || (a[i] === b[i + 1] && a[i + 1] === b[i] && rest(2, 2))
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/*
|
|
479
|
-
* A typo in a word long enough to have one: a slip in a three-letter word
|
|
480
|
-
* makes it a different word. Compared against the word and its start, so
|
|
481
|
-
* "deplyo" finds "deploy" and "deployment" alike.
|
|
482
|
-
*/
|
|
483
|
-
const TYPO_MIN = 5
|
|
484
|
-
function nearWord(term: string, label: string): boolean {
|
|
485
|
-
if (term.length < TYPO_MIN) return false
|
|
486
|
-
return label
|
|
487
|
-
.split(/[^\p{L}\p{N}]+/u)
|
|
488
|
-
.some((word) => withinOne(term, word) || (word.length > term.length && withinOne(term, word.slice(0, term.length))))
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/*
|
|
492
|
-
* Every word of the query has to land somewhere, in any order: "branch deploy"
|
|
493
|
-
* finds "Deploy current branch". Per word, label > keywords > description,
|
|
494
|
-
* the same weights the palette always had; a typo match ranks below all of
|
|
495
|
-
* them, so an exact hit is never outranked by a near one.
|
|
496
|
-
*/
|
|
497
|
-
interface SearchTerms {
|
|
498
|
-
include: string[]
|
|
499
|
-
exclude: string[]
|
|
500
|
-
groups: string[]
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const QUERY_TOKEN = /(-?)(?:(in):)?(?:"([^"]*)"|(\S+))/giu
|
|
504
|
-
|
|
505
|
-
function searchTerms(value: string): SearchTerms {
|
|
506
|
-
const out: SearchTerms = { include: [], exclude: [], groups: [] }
|
|
507
|
-
QUERY_TOKEN.lastIndex = 0
|
|
508
|
-
for (let match; (match = QUERY_TOKEN.exec(value)); ) {
|
|
509
|
-
const text = (match[3] ?? match[4] ?? "").trim().toLowerCase()
|
|
510
|
-
if (!text) continue
|
|
511
|
-
if (match[2] && !match[1]) out.groups.push(text)
|
|
512
|
-
else if (match[1]) out.exclude.push(text)
|
|
513
|
-
else out.include.push(text)
|
|
514
|
-
}
|
|
515
|
-
return out
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function matches(c: Command, term: string): boolean {
|
|
519
|
-
return (
|
|
520
|
-
c.label.toLowerCase().includes(term) ||
|
|
521
|
-
(c.keywords ?? []).some((keyword) => keyword.toLowerCase().includes(term)) ||
|
|
522
|
-
(c.description?.toLowerCase().includes(term) ?? false)
|
|
523
|
-
)
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function score(c: Command, terms: SearchTerms): number {
|
|
527
|
-
const label = c.label.toLowerCase()
|
|
528
|
-
const keywords = (c.keywords ?? []).map((k) => k.toLowerCase())
|
|
529
|
-
const description = c.description?.toLowerCase() ?? ""
|
|
530
|
-
const group = c.group?.toLowerCase() ?? ""
|
|
531
|
-
if (terms.exclude.some((term) => matches(c, term)) || terms.groups.some((term) => !group.includes(term))) return 0
|
|
532
|
-
let total = terms.include.length ? 0 : 1
|
|
533
|
-
for (const term of terms.include) {
|
|
534
|
-
let hit =
|
|
535
|
-
(label.includes(term) ? 4 : 0) +
|
|
536
|
-
(keywords.some((k) => k.includes(term)) ? 3 : 0) +
|
|
537
|
-
(description.includes(term) ? 2 : 0)
|
|
538
|
-
if (!hit && nearWord(term, label)) hit = 1
|
|
539
|
-
if (!hit) return 0
|
|
540
|
-
total += hit
|
|
541
|
-
}
|
|
542
|
-
return total
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function rank(items: Command[], terms: SearchTerms): Command[] {
|
|
546
|
-
return items
|
|
547
|
-
.map((c) => ({ c, score: score(c, terms) }))
|
|
548
|
-
.filter((e) => e.score > 0)
|
|
549
|
-
.sort((a, b) => b.score - a.score || a.c.label.localeCompare(b.c.label))
|
|
550
|
-
.map((e) => e.c)
|
|
551
|
-
}
|
|
552
|
-
|
|
553
459
|
/*
|
|
554
460
|
* The local match and the terms it matched with — the swapped ones when the
|
|
555
461
|
* layout fallback is what found it, so the highlight marks what actually hit.
|
|
556
462
|
*/
|
|
557
463
|
const local = computed<{ items: Command[]; terms: string[] }>(() => {
|
|
558
|
-
const terms =
|
|
464
|
+
const terms = parseCommandQuery(query.value)
|
|
559
465
|
const filtered = query.value.trim()
|
|
560
466
|
? source.value
|
|
561
467
|
: source.value.filter((c) => !c.hidden)
|
|
562
468
|
// Empty query: show everything EXCEPT hidden commands (they only surface once
|
|
563
469
|
// typed). With a query, hidden commands rank like any other.
|
|
564
470
|
if (!terms.include.length && !terms.exclude.length && !terms.groups.length) return { items: filtered, terms: [] }
|
|
565
|
-
const items =
|
|
471
|
+
const items = rankCommandItems(filtered, terms)
|
|
566
472
|
if (items.length || !props.layoutFallback) return { items, terms: terms.include }
|
|
567
|
-
const swapped
|
|
568
|
-
|
|
569
|
-
exclude: terms.exclude.map(swapLayout),
|
|
570
|
-
groups: terms.groups.map(swapLayout),
|
|
571
|
-
}
|
|
572
|
-
const other = rank(filtered, swapped)
|
|
473
|
+
const swapped = swapCommandTerms(terms)
|
|
474
|
+
const other = rankCommandItems(filtered, swapped)
|
|
573
475
|
return other.length ? { items: other, terms: swapped.include } : { items, terms: terms.include }
|
|
574
476
|
})
|
|
575
477
|
|
|
@@ -904,6 +806,8 @@ function onEscape(event: KeyboardEvent) {
|
|
|
904
806
|
watch(
|
|
905
807
|
() => props.open,
|
|
906
808
|
async (o) => {
|
|
809
|
+
if (o) claim()
|
|
810
|
+
else release()
|
|
907
811
|
cancel()
|
|
908
812
|
query.value = ""
|
|
909
813
|
select(0)
|
|
@@ -921,6 +825,8 @@ watch(
|
|
|
921
825
|
{ immediate: true },
|
|
922
826
|
)
|
|
923
827
|
|
|
828
|
+
onBeforeUnmount(release)
|
|
829
|
+
|
|
924
830
|
// Through useHotkeys rather than a listener of its own: the palette is the one
|
|
925
831
|
// shortcut that has to work from inside a text field, and `code` is what makes
|
|
926
832
|
// it fire on a layout where Ctrl-K arrives as "л".
|
|
@@ -943,12 +849,14 @@ useHotkeys(() =>
|
|
|
943
849
|
<DialogRoot :open="open" @update:open="(v) => emit('update:open', v)">
|
|
944
850
|
<DialogPortal>
|
|
945
851
|
<DialogOverlay
|
|
946
|
-
:class="{ 'lp-scrim--light': frosted }"
|
|
947
|
-
class="lp-scrim fixed inset-0
|
|
852
|
+
:class="{ 'lp-scrim--light': frosted, 'lp-scrim--nested': layer.nested }"
|
|
853
|
+
class="lp-scrim fixed inset-0 data-[state=open]:animate-[fade-in_150ms_ease] data-[state=closed]:animate-[fade-out_130ms_ease]"
|
|
854
|
+
:style="{ zIndex: layer.scrim }"
|
|
948
855
|
/>
|
|
949
856
|
<DialogContent
|
|
950
|
-
class="lp-cmd-dialog fixed left-1/2 top-[12vh]
|
|
951
|
-
:class="[frosted ? 'lp-glass-overlay' : 'bg-surface-raised', previewPane && 'lp-cmd-split']"
|
|
857
|
+
class="lp-cmd-dialog fixed left-1/2 top-[12vh] flex max-h-[76vh] -translate-x-1/2 flex-col overflow-hidden rounded-card border border-line shadow-panel outline-none data-[state=open]:animate-[pop-in_160ms_var(--ease-emphasized)] data-[state=closed]:animate-[pop-out_130ms_ease]"
|
|
858
|
+
:class="[frosted ? (layer.nested ? 'lp-glass-overlay--nested' : 'lp-glass-overlay') : 'bg-surface-raised', previewPane && 'lp-cmd-split']"
|
|
859
|
+
:style="{ zIndex: layer.panel }"
|
|
952
860
|
:data-pane="paneOpen ? 'open' : undefined"
|
|
953
861
|
@open-auto-focus.prevent
|
|
954
862
|
@keydown.capture="onPaletteKeydown"
|
|
@@ -153,7 +153,7 @@ watch(() => props.open, (open) => (open ? claim() : release()), { immediate: tru
|
|
|
153
153
|
onBeforeUnmount(release)
|
|
154
154
|
|
|
155
155
|
const contentClass = computed(() => {
|
|
156
|
-
const surface = frosted.value ? "lp-glass-overlay" : "bg-surface-raised"
|
|
156
|
+
const surface = frosted.value ? (layer.value.nested ? "lp-glass-overlay--nested" : "lp-glass-overlay") : "bg-surface-raised"
|
|
157
157
|
const base = `fixed z-(--z-modal) flex flex-col border-line ${surface} shadow-panel outline-none`
|
|
158
158
|
const map: Record<Direction, string> = {
|
|
159
159
|
left: "inset-y-0 left-0 border-r rounded-r-card",
|
|
@@ -443,7 +443,7 @@ const previewStyle = computed(() => {
|
|
|
443
443
|
<div
|
|
444
444
|
v-show="peeking || peek > 0"
|
|
445
445
|
class="lp-scrim pointer-events-none fixed inset-0"
|
|
446
|
-
:class="{ 'lp-scrim--light': frosted }"
|
|
446
|
+
:class="{ 'lp-scrim--light': frosted, 'lp-scrim--nested': layer.nested }"
|
|
447
447
|
:style="{ zIndex: layer.scrim, opacity: peek, transition: peeking || reducedMotion() ? 'none' : 'opacity 0.18s ease-out' }"
|
|
448
448
|
/>
|
|
449
449
|
<div
|
|
@@ -476,7 +476,7 @@ const previewStyle = computed(() => {
|
|
|
476
476
|
<DrawerOverlay
|
|
477
477
|
:style="{ zIndex: layer.scrim }"
|
|
478
478
|
class="lp-scrim fixed inset-0 z-(--z-overlay) data-[state=open]:animate-[fade-in_150ms_ease] data-[state=closed]:animate-[fade-out_130ms_ease]"
|
|
479
|
-
:class="{ 'lp-scrim--light': frosted }"
|
|
479
|
+
:class="{ 'lp-scrim--light': frosted, 'lp-scrim--nested': layer.nested }"
|
|
480
480
|
/>
|
|
481
481
|
<DrawerContent
|
|
482
482
|
:class="contentClass"
|
|
@@ -21,10 +21,11 @@ import {
|
|
|
21
21
|
DialogRoot,
|
|
22
22
|
DialogTitle,
|
|
23
23
|
} from "reka-ui"
|
|
24
|
-
import { computed, nextTick, ref, watch } from "vue"
|
|
24
|
+
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue"
|
|
25
25
|
import { useZoomPan } from "../composables/useZoomPan"
|
|
26
26
|
import { fileNameOf, type LightboxItem } from "./lightbox"
|
|
27
27
|
import LpIcon from "./LpIcon.vue"
|
|
28
|
+
import { useModalLayer } from "./modalDepth"
|
|
28
29
|
|
|
29
30
|
const props = withDefaults(
|
|
30
31
|
defineProps<{
|
|
@@ -67,6 +68,10 @@ const emit = defineEmits<{
|
|
|
67
68
|
(e: "copy", item: LightboxItem, ok: boolean): void
|
|
68
69
|
}>()
|
|
69
70
|
|
|
71
|
+
const { claim, release, layer } = useModalLayer()
|
|
72
|
+
watch(() => props.open, (open) => (open ? claim() : release()), { immediate: true })
|
|
73
|
+
onBeforeUnmount(release)
|
|
74
|
+
|
|
70
75
|
defineSlots<{
|
|
71
76
|
/** Replace the caption block under the image. */
|
|
72
77
|
caption(props: { item: LightboxItem; index: number; total: number }): unknown
|
|
@@ -335,13 +340,14 @@ const ARROW =
|
|
|
335
340
|
<template>
|
|
336
341
|
<DialogRoot :open="open" @update:open="(v) => emit('update:open', v)">
|
|
337
342
|
<DialogPortal>
|
|
338
|
-
<!-- Sits on the lightbox rung, not the shared modal one: opening this
|
|
339
|
-
from inside a dialog must cover that dialog, not tie with it. -->
|
|
340
343
|
<DialogOverlay
|
|
341
|
-
class="lp-scrim
|
|
344
|
+
class="lp-scrim fixed inset-0 data-[state=open]:animate-[fade-in_var(--duration-medium)_var(--ease-emphasized)] data-[state=closed]:animate-[fade-out_120ms_ease]"
|
|
345
|
+
:class="{ 'lp-scrim--nested': layer.nested }"
|
|
346
|
+
:style="{ zIndex: layer.scrim }"
|
|
342
347
|
/>
|
|
343
348
|
<DialogContent
|
|
344
|
-
class="fixed inset-0
|
|
349
|
+
class="fixed inset-0 flex flex-col outline-none data-[state=open]:animate-[lightbox-in_var(--duration-medium)_var(--ease-emphasized)] data-[state=closed]:animate-[lightbox-out_140ms_cubic-bezier(0.4,0,1,1)] motion-reduce:animate-none"
|
|
350
|
+
:style="{ zIndex: layer.panel }"
|
|
345
351
|
@keydown="onKeydown"
|
|
346
352
|
>
|
|
347
353
|
<!-- reka wants both for a labelled dialog; the caption is the title. -->
|
|
@@ -232,7 +232,7 @@ watch(
|
|
|
232
232
|
<DialogRoot :open="open" @update:open="(v) => $emit('update:open', v)">
|
|
233
233
|
<DialogPortal>
|
|
234
234
|
<DialogOverlay
|
|
235
|
-
:class="{ 'lp-scrim--light': frosted }"
|
|
235
|
+
:class="{ 'lp-scrim--light': frosted, 'lp-scrim--nested': layer.nested }"
|
|
236
236
|
class="lp-scrim fixed inset-0 data-[state=open]:animate-[fade-in_var(--duration-medium)_var(--ease-emphasized)] data-[state=closed]:animate-[fade-out_120ms_ease]"
|
|
237
237
|
:style="{ zIndex: layer.scrim }"
|
|
238
238
|
/>
|
|
@@ -302,7 +302,7 @@ watch(
|
|
|
302
302
|
<div
|
|
303
303
|
ref="panelRef"
|
|
304
304
|
class="flex max-h-[min(90vh,calc(100dvh-2rem))] min-h-0 flex-col overflow-hidden rounded-card border border-line shadow-panel"
|
|
305
|
-
:class="[frosted ? 'lp-glass-overlay' : 'bg-surface-raised', widthClass, tweening ? 'transition-[height] duration-fast ease-[var(--ease-emphasized)] motion-reduce:transition-none' : '']"
|
|
305
|
+
:class="[frosted ? (layer.nested ? 'lp-glass-overlay--nested' : 'lp-glass-overlay') : 'bg-surface-raised', widthClass, tweening ? 'transition-[height] duration-fast ease-[var(--ease-emphasized)] motion-reduce:transition-none' : '']"
|
|
306
306
|
:style="{ ...(width ? { width } : {}), ...(hasAside ? { alignSelf: 'stretch' } : {}) }"
|
|
307
307
|
>
|
|
308
308
|
<header v-if="title || $slots.title" class="flex shrink-0 items-start justify-between gap-4 p-5 pb-3">
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export interface SearchableCommand {
|
|
2
|
+
label: string
|
|
3
|
+
description?: string
|
|
4
|
+
keywords?: string[]
|
|
5
|
+
group?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CommandSearchTerms {
|
|
9
|
+
include: string[]
|
|
10
|
+
exclude: string[]
|
|
11
|
+
groups: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const QUERY_TOKEN = /(-?)(?:(in):)?(?:"([^"]*)"|(\S+))/giu
|
|
15
|
+
const LATIN = "qwertyuiop[]asdfghjkl;'zxcvbnm,.`"
|
|
16
|
+
const CYRILLIC = "йцукенгшщзхъфывапролджэячсмитьбюё"
|
|
17
|
+
const SWAP = new Map<string, string>()
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < LATIN.length; i++) {
|
|
20
|
+
SWAP.set(LATIN[i]!, CYRILLIC[i]!)
|
|
21
|
+
SWAP.set(CYRILLIC[i]!, LATIN[i]!)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseCommandQuery(value: string): CommandSearchTerms {
|
|
25
|
+
const out: CommandSearchTerms = { include: [], exclude: [], groups: [] }
|
|
26
|
+
QUERY_TOKEN.lastIndex = 0
|
|
27
|
+
for (let match; (match = QUERY_TOKEN.exec(value)); ) {
|
|
28
|
+
const text = (match[3] ?? match[4] ?? "").trim().toLowerCase()
|
|
29
|
+
if (!text) continue
|
|
30
|
+
if (match[2] && !match[1]) out.groups.push(text)
|
|
31
|
+
else if (match[1]) out.exclude.push(text)
|
|
32
|
+
else out.include.push(text)
|
|
33
|
+
}
|
|
34
|
+
return out
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function swapCommandTerms(terms: CommandSearchTerms): CommandSearchTerms {
|
|
38
|
+
const swap = (term: string) => [...term].map((ch) => SWAP.get(ch) ?? ch).join("")
|
|
39
|
+
return {
|
|
40
|
+
include: terms.include.map(swap),
|
|
41
|
+
exclude: terms.exclude.map(swap),
|
|
42
|
+
groups: terms.groups.map(swap),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function matches(command: SearchableCommand, term: string): boolean {
|
|
47
|
+
return (
|
|
48
|
+
command.label.toLowerCase().includes(term)
|
|
49
|
+
|| (command.keywords ?? []).some((keyword) => keyword.toLowerCase().includes(term))
|
|
50
|
+
|| (command.description?.toLowerCase().includes(term) ?? false)
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function withinOne(a: string, b: string): boolean {
|
|
55
|
+
if (Math.abs(a.length - b.length) > 1) return false
|
|
56
|
+
let i = 0
|
|
57
|
+
while (i < a.length && i < b.length && a[i] === b[i]) i++
|
|
58
|
+
if (i === a.length || i === b.length) return true
|
|
59
|
+
const rest = (x: number, y: number) => a.slice(i + x) === b.slice(i + y)
|
|
60
|
+
return rest(1, 1) || rest(1, 0) || rest(0, 1) || (a[i] === b[i + 1] && a[i + 1] === b[i] && rest(2, 2))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function nearWord(term: string, label: string): boolean {
|
|
64
|
+
if (term.length < 5) return false
|
|
65
|
+
return label
|
|
66
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
67
|
+
.some((word) => withinOne(term, word) || (word.length > term.length && withinOne(term, word.slice(0, term.length))))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function score(command: SearchableCommand, terms: CommandSearchTerms): number {
|
|
71
|
+
const label = command.label.toLowerCase()
|
|
72
|
+
const keywords = (command.keywords ?? []).map((keyword) => keyword.toLowerCase())
|
|
73
|
+
const description = command.description?.toLowerCase() ?? ""
|
|
74
|
+
const group = command.group?.toLowerCase() ?? ""
|
|
75
|
+
if (terms.exclude.some((term) => matches(command, term)) || terms.groups.some((term) => !group.includes(term))) return 0
|
|
76
|
+
let total = terms.include.length ? 0 : 1
|
|
77
|
+
for (const term of terms.include) {
|
|
78
|
+
let hit = (label.includes(term) ? 4 : 0) + (keywords.some((keyword) => keyword.includes(term)) ? 3 : 0) + (description.includes(term) ? 2 : 0)
|
|
79
|
+
if (!hit && nearWord(term, label)) hit = 1
|
|
80
|
+
if (!hit) return 0
|
|
81
|
+
total += hit
|
|
82
|
+
}
|
|
83
|
+
return total
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function rankCommandItems<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): T[] {
|
|
87
|
+
return items
|
|
88
|
+
.map((command) => ({ command, score: score(command, terms) }))
|
|
89
|
+
.filter((entry) => entry.score > 0)
|
|
90
|
+
.sort((a, b) => b.score - a.score || a.command.label.localeCompare(b.command.label))
|
|
91
|
+
.map((entry) => entry.command)
|
|
92
|
+
}
|
|
@@ -1,61 +1,17 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Stacking order for dialogs that are open at the same time.
|
|
3
|
-
*
|
|
4
|
-
* Not provide/inject, which was the first shape tried: it only works when the
|
|
5
|
-
* second dialog is rendered INSIDE the first one's subtree, and the ordinary
|
|
6
|
-
* way to write a dialog opened from a dialog is to put both at the top level of
|
|
7
|
-
* the same component and toggle them with separate flags. Those two are
|
|
8
|
-
* siblings, so the inner one injects nothing and lands on the same rungs as the
|
|
9
|
-
* outer — which is the bug.
|
|
10
|
-
*
|
|
11
|
-
* What decides the order is the sequence of OPENING: a dialog opened while
|
|
12
|
-
* another is up belongs above it, wherever it sits in the markup.
|
|
13
|
-
*
|
|
14
|
-
* The newest dialog keeps the scale's own --z-overlay/--z-modal and the ones
|
|
15
|
-
* beneath are pushed DOWN, rather than each new dialog climbing higher. Climbing
|
|
16
|
-
* runs out of room immediately: --z-popover is only 10 above --z-modal, so the
|
|
17
|
-
* second nested dialog would tie with every popover and the third with every
|
|
18
|
-
* tooltip — and a select inside a dialog is ordinary. Descending has no such
|
|
19
|
-
* ceiling, and it keeps the frontmost dialog exactly where a single one has
|
|
20
|
-
* always been.
|
|
21
|
-
*/
|
|
22
1
|
import { computed, ref, type ComputedRef } from "vue"
|
|
23
2
|
|
|
24
|
-
/*
|
|
25
|
-
* Rungs per level. Has to clear a whole level, not just its scrim: --z-overlay
|
|
26
|
-
* is 100 and --z-modal 110, so a gap of 2 left the upper scrim under the lower
|
|
27
|
-
* PANEL — it darkened that panel and could not blur it, a backdrop-filter
|
|
28
|
-
* having nothing of it underneath to work on.
|
|
29
|
-
*
|
|
30
|
-
* Clearing it means strictly more than the 10 between the two rungs. A step of
|
|
31
|
-
* exactly 10 put the lower panel at 100, level with the upper scrim, and a tie
|
|
32
|
-
* in z-index is settled by DOM order. Siblings in one component happened to
|
|
33
|
-
* come out right, but a dialog mounted once at the app root — opened from a
|
|
34
|
-
* page dialog that teleported in later — lost the tie, and its scrim darkened
|
|
35
|
-
* the page behind the first dialog instead of the first dialog itself.
|
|
36
|
-
*/
|
|
37
3
|
const STEP = 20
|
|
38
4
|
|
|
39
|
-
/** Levels currently claimed, oldest first. */
|
|
40
5
|
const stack = ref<number[]>([])
|
|
41
6
|
let nextId = 0
|
|
42
7
|
|
|
43
|
-
/**
|
|
44
|
-
* Register a dialog as open and get back the z-indexes it should paint with.
|
|
45
|
-
* The result is reactive: opening another dialog moves this one down a step.
|
|
46
|
-
*/
|
|
47
8
|
export function useModalLayer(): {
|
|
48
9
|
claim: () => void
|
|
49
10
|
release: () => void
|
|
50
|
-
layer: ComputedRef<{ scrim: string; panel: string }>
|
|
11
|
+
layer: ComputedRef<{ scrim: string; panel: string; nested: boolean }>
|
|
51
12
|
} {
|
|
52
13
|
const id = nextId++
|
|
53
14
|
|
|
54
|
-
/*
|
|
55
|
-
* How far from the TOP of the stack, so the newest dialog is 0 and keeps the
|
|
56
|
-
* unmodified rungs. A dialog that is not in the stack reads 0 too: it is
|
|
57
|
-
* either closed, or opening this very frame and about to claim.
|
|
58
|
-
*/
|
|
59
15
|
const fromTop = computed(() => {
|
|
60
16
|
const at = stack.value.indexOf(id)
|
|
61
17
|
return at === -1 ? 0 : stack.value.length - 1 - at
|
|
@@ -71,14 +27,14 @@ export function useModalLayer(): {
|
|
|
71
27
|
layer: computed(() => {
|
|
72
28
|
const drop = fromTop.value * STEP
|
|
73
29
|
return {
|
|
74
|
-
scrim: `calc(var(--z-
|
|
75
|
-
panel: `calc(var(--z-
|
|
30
|
+
scrim: `calc(var(--z-toast) - 20 - ${drop})`,
|
|
31
|
+
panel: `calc(var(--z-toast) - 10 - ${drop})`,
|
|
32
|
+
nested: stack.value.indexOf(id) > 0,
|
|
76
33
|
}
|
|
77
34
|
}),
|
|
78
35
|
}
|
|
79
36
|
}
|
|
80
37
|
|
|
81
|
-
/** True while any dialog or drawer holds a level — e.g. to keep a gesture from opening another one over it. */
|
|
82
38
|
export function anyModalOpen(): boolean {
|
|
83
39
|
return stack.value.length > 0
|
|
84
40
|
}
|
package/src/index.ts
CHANGED
|
@@ -65,6 +65,8 @@ export {
|
|
|
65
65
|
export type { ColorFormat, Hsv, Rgb } from "./components/color"
|
|
66
66
|
export { default as LpCommandPalette } from "./components/LpCommandPalette.vue"
|
|
67
67
|
export type { Command, PaletteLabels } from "./components/LpCommandPalette.vue"
|
|
68
|
+
export { parseCommandQuery, rankCommandItems, swapCommandTerms } from "./components/commandSearch"
|
|
69
|
+
export type { CommandSearchTerms, SearchableCommand } from "./components/commandSearch"
|
|
68
70
|
export { default as LpConfirmDialog } from "./components/LpConfirmDialog.vue"
|
|
69
71
|
export { default as LpContextMenu } from "./components/LpContextMenu.vue"
|
|
70
72
|
export type { ContextMenuItemDef } from "./components/LpContextMenu.vue"
|
package/src/tokens/tokens.css
CHANGED
|
@@ -171,14 +171,6 @@
|
|
|
171
171
|
--z-tooltip: 130;
|
|
172
172
|
--z-toast: 200;
|
|
173
173
|
|
|
174
|
-
/* A lightbox is usually opened FROM a dialog — click a thumbnail in a modal
|
|
175
|
-
and the full image comes up over it. Sharing --z-overlay/--z-modal left
|
|
176
|
-
that order to DOM insertion alone, so the lightbox's own scrim landed on
|
|
177
|
-
top of the modal panel and swallowed it while double-darkening the page
|
|
178
|
-
behind. Its own rung above the modal (but below tooltip/toast, which must
|
|
179
|
-
outrank everything) makes the stack explicit. */
|
|
180
|
-
--z-lightbox-overlay: 140;
|
|
181
|
-
--z-lightbox: 150;
|
|
182
174
|
}
|
|
183
175
|
|
|
184
176
|
/* ── overlay enter/exit animations ───────────────
|
|
@@ -511,17 +503,6 @@
|
|
|
511
503
|
--scrim-opacity: 0.62;
|
|
512
504
|
}
|
|
513
505
|
|
|
514
|
-
/*
|
|
515
|
-
* A lightbox is the one scrim that hides a whole page behind a photo, and it is
|
|
516
|
-
* usually opened FROM a dialog — so it is the layer the mild shared tint serves
|
|
517
|
-
* worst: without the blur, the content behind stayed readable around the image.
|
|
518
|
-
* It gets its own value instead of raising the shared one, which would darken
|
|
519
|
-
* every stacked pair.
|
|
520
|
-
*/
|
|
521
|
-
:root[data-lp-no-backdrop] .lp-scrim--deep {
|
|
522
|
-
--scrim-opacity: 0.78;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
506
|
/*
|
|
526
507
|
* Under a glass panel (.lp-glass-overlay) the usual tint leaves nothing but
|
|
527
508
|
* grey to see through it, so the scrim dims less and the panel's own blur
|
|
@@ -532,6 +513,12 @@
|
|
|
532
513
|
--scrim-opacity: 0.32;
|
|
533
514
|
}
|
|
534
515
|
|
|
516
|
+
.lp-scrim--nested {
|
|
517
|
+
--scrim-opacity: 0.24;
|
|
518
|
+
-webkit-backdrop-filter: none;
|
|
519
|
+
backdrop-filter: none;
|
|
520
|
+
}
|
|
521
|
+
|
|
535
522
|
/*
|
|
536
523
|
* Frosted overlay: a floating panel that lets the page show through, softened.
|
|
537
524
|
* For surfaces that float over everything and are read briefly (the command
|
|
@@ -550,6 +537,13 @@
|
|
|
550
537
|
backdrop-filter: blur(var(--glass-overlay-blur, 28px)) saturate(170%);
|
|
551
538
|
}
|
|
552
539
|
|
|
540
|
+
.lp-glass-overlay--nested {
|
|
541
|
+
background-color: color-mix(in srgb, var(--color-surface-raised) 88%, transparent);
|
|
542
|
+
background-image: none;
|
|
543
|
+
-webkit-backdrop-filter: none;
|
|
544
|
+
backdrop-filter: none;
|
|
545
|
+
}
|
|
546
|
+
|
|
553
547
|
:root[data-lp-no-backdrop] .lp-glass-overlay {
|
|
554
548
|
background-color: var(--color-surface-raised);
|
|
555
549
|
background-image: none;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const COMPONENT_NAMES: readonly ["LayoutCanvas", "LayoutNode", "LpActionBar", "LpAddressInput", "LpAlert", "LpAppShell", "LpAutocomplete", "LpAvatar", "LpBadge", "LpBreadcrumbs", "LpButton", "LpCalendar", "LpCard", "LpChart", "LpCheckbox", "LpCodeBlock", "LpColorPicker", "LpCommandPalette", "LpConfirmDialog", "LpContextMenu", "LpDatePicker", "LpDisclosure", "LpDivider", "LpDrawer", "LpDropdownMenu", "LpDropzone", "LpEmptyState", "LpFileTree", "LpFileTreeNode", "LpFormField", "LpIcon", "LpImageEditor", "LpInfraNode", "LpInput", "LpLaneNode", "LpLightbox", "LpLink", "LpLogViewer", "LpMapPicker", "LpMediaGallery", "LpModal", "LpModalNav", "LpNotificationBell", "LpNumberField", "LpNumberFlow", "LpOtpInput", "LpPagination", "LpPasswordInput", "LpPhoneInput", "LpPie", "LpPopover", "LpProgress", "LpRadio", "LpRadioGroup", "LpRollingText", "LpScrollArea", "LpSection", "LpSegmented", "LpSelect", "LpServiceNode", "LpShift", "LpShortcutsDialog", "LpSidebar", "LpSidebarNav", "LpSkeleton", "LpSlider", "LpSparkline", "LpStat", "LpStepper", "LpSwitch", "LpTable", "LpTableOfContents", "LpTabs", "LpTextarea", "LpThemeSwitcher", "LpTilt", "LpTimeField", "LpToaster", "LpTooltip", "LpTopologyCanvas", "LpUptimeBar", "LpWaypointEdge"];
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Split } from '../layout/tree';
|
|
2
|
-
import { BlockRegistry } from '../layout/registry';
|
|
3
|
-
type __VLS_Props = {
|
|
4
|
-
modelValue: Split;
|
|
5
|
-
edit?: boolean;
|
|
6
|
-
/** Catalogue mapping block ids to titles/icons for the tab bars. */
|
|
7
|
-
registry?: BlockRegistry;
|
|
8
|
-
};
|
|
9
|
-
type __VLS_Slots = {
|
|
10
|
-
block(props: {
|
|
11
|
-
block: string;
|
|
12
|
-
leafId: string;
|
|
13
|
-
edit: boolean;
|
|
14
|
-
}): unknown;
|
|
15
|
-
};
|
|
16
|
-
declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
|
|
17
|
-
"update:modelValue": (value: Split) => any;
|
|
18
|
-
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
19
|
-
"onUpdate:modelValue"?: ((value: Split) => any) | undefined;
|
|
20
|
-
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
21
|
-
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
22
|
-
declare const _default: typeof __VLS_export;
|
|
23
|
-
export default _default;
|
|
24
|
-
type __VLS_WithSlots<T, S> = T & {
|
|
25
|
-
new (): {
|
|
26
|
-
$slots: S;
|
|
27
|
-
};
|
|
28
|
-
};
|