@leavepulse/ui 0.71.0 → 0.71.2
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-DgPZX0us.css → LpCommandPalette-Rtbg669l.css} +19 -19
- package/dist/assets/{LpNumberFlow-rZkwQwlU.css → LpNumberFlow-Chp9pEDe.css} +8 -0
- package/dist/assets/LpTabs-DT0XPTc2.css +18 -0
- package/dist/chunks/{LpAddressInput-CD0bzrt-.js → LpAddressInput-Bu6ws3Fp.js} +1 -1
- package/dist/chunks/{LpAppShell-1RmBp6xQ.js → LpAppShell-C5YW5pI7.js} +1 -1
- package/dist/chunks/{LpCodeDisplay-BK2FHbtM.js → LpCodeDisplay-C9pIMF2R.js} +1 -1
- package/dist/chunks/{LpCommandScope-ubtWDMa0.js → LpCommandScope-BlKcgBnK.js} +1 -1
- package/dist/chunks/{LpCountdown-DTdMF8DL.js → LpCountdown-CXrEK0ev.js} +1 -1
- package/dist/chunks/{LpGauge-BdjPGnqw.js → LpGauge-DFdt33x8.js} +1 -1
- package/dist/chunks/{LpModalNav-BH4ANQGJ.js → LpModalNav-Cuna8c_3.js} +1 -1
- package/dist/chunks/{LpNumberFlow-5khYxsfX.js → LpNumberFlow-Dwb9MV1A.js} +6 -5
- package/dist/chunks/{LpPaletteTheme-CqWNRwHE.js → LpPaletteTheme-DkikZ362.js} +5 -3
- package/dist/chunks/{LpPie-DdFSTbGE.js → LpPie-C1uIUFxu.js} +1 -1
- package/dist/chunks/{LpProgress-BX33EKkN.js → LpProgress-WNaH4emv.js} +1 -1
- package/dist/chunks/{LpQuota-DqNv1yjs.js → LpQuota-Cz9PvklM.js} +1 -1
- package/dist/chunks/{LpSelect-DYX2-u5A.js → LpSelect-D3pRJfRY.js} +4 -4
- package/dist/chunks/{LpSidebar-D44peKq2.js → LpSidebar-Bk7b-K4l.js} +1 -1
- package/dist/chunks/{LpSidebarNav--VwkXO_a.js → LpSidebarNav-BR2AW3KT.js} +6 -1
- package/dist/chunks/{LpStat-DIsQfbeD.js → LpStat-Dzn6WFzi.js} +4 -4
- package/dist/chunks/{LpThemeStudio-B2nPaG-h.js → LpThemeStudio-IupHqP0x.js} +2 -2
- package/dist/chunks/{LpThemeSwitcher-C0A5sg_E.js → LpThemeSwitcher-DarPHuX8.js} +4 -3
- package/dist/chunks/{LpTopologyCanvas-B0wEvAoq.js → LpTopologyCanvas-Cq-MiJwo.js} +42 -12
- package/dist/chunks/{LpUptimeBar-DRadGq4Y.js → LpUptimeBar-BbhVTPnL.js} +1 -1
- package/dist/chunks/{palette-BiY7yQZL.js → palette-Cvxl_1tM.js} +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpChart.vue.js +22 -4
- package/dist/components/LpCodeDisplay.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +43 -22
- package/dist/components/LpCommandScope.vue.js +1 -1
- package/dist/components/LpCountdown.vue.js +1 -1
- package/dist/components/LpGauge.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpPaletteStats.vue.js +1 -1
- package/dist/components/LpPaletteTheme.vue.d.ts +2 -0
- package/dist/components/LpPaletteTheme.vue.js +1 -1
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpQuota.vue.js +1 -1
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +8 -0
- package/dist/components/LpTabs.vue.js +7 -5
- package/dist/components/LpThemeStudio.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.d.ts +3 -1
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +14 -0
- package/dist/components/chart.js +13 -0
- package/dist/components/format.d.ts +10 -3
- package/dist/components/format.js +14 -2
- package/dist/composables/usePaletteCommands.js +8 -1
- package/dist/composables/useReveal.js +1 -22
- package/dist/index.d.ts +1 -1
- package/dist/index.js +21 -20
- package/dist/theme/palette.d.ts +3 -0
- package/package.json +1 -1
- package/src/components/LpChart.vue +23 -3
- package/src/components/LpCommandPalette.vue +23 -5
- package/src/components/LpNumberFlow.vue +19 -7
- package/src/components/LpPaletteTheme.vue +3 -3
- package/src/components/LpSelect.vue +5 -4
- package/src/components/LpSidebarNav.vue +8 -1
- package/src/components/LpStat.vue +5 -3
- package/src/components/LpTabs.vue +10 -2
- package/src/components/LpThemeStudio.vue +1 -1
- package/src/components/LpThemeSwitcher.vue +4 -1
- package/src/components/LpTopologyCanvas.vue +35 -4
- package/src/components/chart.ts +26 -0
- package/src/components/format.ts +31 -5
- package/src/composables/usePaletteCommands.ts +10 -1
- package/src/composables/useReveal.ts +7 -30
- package/src/index.ts +1 -1
- package/src/theme/palette.ts +4 -2
- package/dist/assets/LpTabs-CO3EcsNV.css +0 -18
package/dist/index.d.ts
CHANGED
|
@@ -192,7 +192,7 @@ export type { RevealOrigin } from "./theme/useTheme";
|
|
|
192
192
|
export { detectBackdropFilter, GLASS_BACKDROP_ATTR, isBrokenBackdropEngine, NO_BACKDROP_ATTR, setGlassBackdrop } from "./theme/backdrop";
|
|
193
193
|
export { DEFAULT_SURFACE } from "./theme/tokens";
|
|
194
194
|
export { defineTheme, withBrand } from "./theme/define";
|
|
195
|
-
export { STUDIO_BRANDS, STUDIO_CORNERS, STUDIO_MOTION, STUDIO_SIZES, useThemeStudio } from "./theme/studio";
|
|
195
|
+
export { STUDIO_BRANDS, STUDIO_CORNERS, STUDIO_MOTION, STUDIO_SIZES, STUDIO_SYSTEM, useThemeStudio } from "./theme/studio";
|
|
196
196
|
export type { StudioState, ThemeStudio, ThemeStudioOptions } from "./theme/studio";
|
|
197
197
|
export type { ThemeOverrides } from "./theme/define";
|
|
198
198
|
export type { ColorTokens, DensityTokens, FontTokens, ShapeTokens, SurfaceTokens, TokenSet, } from "./theme/tokens";
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { _ } from "./chunks/LayoutCanvas-Ba4A9QC5.js";
|
|
2
|
-
import { _ as _2 } from "./chunks/LpAddressInput-
|
|
2
|
+
import { _ as _2 } from "./chunks/LpAddressInput-Bu6ws3Fp.js";
|
|
3
3
|
import { _ as _3 } from "./chunks/LpMapPicker-Dp5RSCi9.js";
|
|
4
4
|
import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
|
|
5
5
|
import { _ as _4 } from "./chunks/LpActionBar-B7anGlkI.js";
|
|
6
6
|
import { _ as _5 } from "./chunks/LpAlert-BHi7ARqu.js";
|
|
7
|
-
import { _ as _6 } from "./chunks/LpAppShell-
|
|
7
|
+
import { _ as _6 } from "./chunks/LpAppShell-C5YW5pI7.js";
|
|
8
8
|
import { _ as _7 } from "./chunks/LpAutocomplete-DDqe2M5g.js";
|
|
9
9
|
import { _ as _8 } from "./chunks/LpAvatar-DqfKCIdr.js";
|
|
10
10
|
import { _ as _9 } from "./chunks/LpBadge-C8fKEmTF.js";
|
|
@@ -60,40 +60,40 @@ import { _ as _37 } from "./chunks/LpLogViewer-x78YfhJf.js";
|
|
|
60
60
|
import { countLevels, matchPositions, matchesTerm, ringWindow, stepMatch } from "./components/logSearch.js";
|
|
61
61
|
import { _ as _38 } from "./chunks/LpMediaGallery-D92oJfY_.js";
|
|
62
62
|
import { _ as _39 } from "./chunks/LpModal-CpH0bNo7.js";
|
|
63
|
-
import { _ as _40 } from "./chunks/LpModalNav-
|
|
63
|
+
import { _ as _40 } from "./chunks/LpModalNav-Cuna8c_3.js";
|
|
64
64
|
import { _ as _41 } from "./chunks/LpNotificationBell-BdUQqZhs.js";
|
|
65
65
|
import { _ as _42 } from "./chunks/LpNumberField-DZY3Z670.js";
|
|
66
66
|
import { _ as _43 } from "./chunks/LpTimeField-DQ8WvPr2.js";
|
|
67
|
-
import { _ as _44 } from "./chunks/LpNumberFlow-
|
|
67
|
+
import { _ as _44 } from "./chunks/LpNumberFlow-Dwb9MV1A.js";
|
|
68
68
|
import { _ as _45 } from "./chunks/LpOtpInput-CeBPsM7D.js";
|
|
69
69
|
import { _ as _46 } from "./chunks/LpPagination-DbeACavM.js";
|
|
70
70
|
import { _ as _47 } from "./chunks/LpPasswordInput-BzAZsf5V.js";
|
|
71
|
-
import { _ as _48 } from "./chunks/LpPie-
|
|
71
|
+
import { _ as _48 } from "./chunks/LpPie-C1uIUFxu.js";
|
|
72
72
|
import { _ as _49 } from "./chunks/LpPhoneInput-BMpd-0QR.js";
|
|
73
73
|
import { filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
|
|
74
74
|
import { _ as _50 } from "./chunks/LpPopover-DmZ3wUtP.js";
|
|
75
|
-
import { _ as _51 } from "./chunks/LpProgress-
|
|
75
|
+
import { _ as _51 } from "./chunks/LpProgress-WNaH4emv.js";
|
|
76
76
|
import { _ as _52 } from "./chunks/LpRadio-CWA5Eb3M.js";
|
|
77
77
|
import { _ as _53 } from "./chunks/LpRadioGroup-CsXeV9Ct.js";
|
|
78
78
|
import { _ as _54 } from "./chunks/LpRollingText-DiL717LK.js";
|
|
79
79
|
import { _ as _55 } from "./chunks/LpScrollArea-Hz3-JWUG.js";
|
|
80
80
|
import { _ as _56 } from "./chunks/LpSegmented-D2VST0o3.js";
|
|
81
81
|
import { _ as _57 } from "./chunks/LpSection-DcQsieHG.js";
|
|
82
|
-
import { _ as _58 } from "./chunks/LpSelect-
|
|
82
|
+
import { _ as _58 } from "./chunks/LpSelect-D3pRJfRY.js";
|
|
83
83
|
import { fitChips, toggledValues } from "./components/select.js";
|
|
84
84
|
import { _ as _59 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
|
|
85
85
|
import { _ as _60 } from "./chunks/LpShift-DXNsauQZ.js";
|
|
86
86
|
import { _ as _61 } from "./chunks/LpSwap-DzE6M3Mg.js";
|
|
87
87
|
import { _ as _62 } from "./chunks/LpHintExample-9Py0wc-L.js";
|
|
88
88
|
import { default as default11 } from "./components/LpShortcutsDialog.vue.js";
|
|
89
|
-
import { _ as _63 } from "./chunks/LpSidebar-
|
|
89
|
+
import { _ as _63 } from "./chunks/LpSidebar-Bk7b-K4l.js";
|
|
90
90
|
import { _ as _64 } from "./chunks/LpSkeleton-BB6XIV-w.js";
|
|
91
91
|
import { _ as _65 } from "./chunks/LpSlider-DxYoCJJO.js";
|
|
92
92
|
import { _ as _66 } from "./chunks/LpRangeSlider-CM-0ZITZ.js";
|
|
93
93
|
import { _ as _67 } from "./chunks/LpVideoBackdrop-DS1wGv-X.js";
|
|
94
94
|
import { formatClipTime, isNativeLoop, planLoop, seamProgress, seamReached } from "./components/videoBackdrop.js";
|
|
95
95
|
import { _ as _68 } from "./chunks/LpSparkline-CExIRDb3.js";
|
|
96
|
-
import { _ as _69 } from "./chunks/LpStat-
|
|
96
|
+
import { _ as _69 } from "./chunks/LpStat-Dzn6WFzi.js";
|
|
97
97
|
import { _ as _70 } from "./chunks/LpStepper-C9RM_yTG.js";
|
|
98
98
|
import { _ as _71 } from "./chunks/LpSwitch-bGZxUDSx.js";
|
|
99
99
|
import { default as default12 } from "./components/LpTable.vue.js";
|
|
@@ -104,7 +104,7 @@ import { default as default13 } from "./components/LpTabs.vue.js";
|
|
|
104
104
|
import { _ as _73 } from "./chunks/LpTagsInput-DrjmrOge.js";
|
|
105
105
|
import { addTags, splitTags } from "./components/tags.js";
|
|
106
106
|
import { _ as _74 } from "./chunks/LpTextarea-BbYqmG1S.js";
|
|
107
|
-
import { _ as _75 } from "./chunks/LpThemeSwitcher-
|
|
107
|
+
import { _ as _75 } from "./chunks/LpThemeSwitcher-DarPHuX8.js";
|
|
108
108
|
import { _ as _76 } from "./chunks/LpTilt-B7KHX1zH.js";
|
|
109
109
|
import { _ as _77 } from "./chunks/LpToaster-BTTl_wuy.js";
|
|
110
110
|
import { _ as _78 } from "./chunks/LpTooltip-BcBvIx4z.js";
|
|
@@ -114,8 +114,8 @@ import { _ as _80 } from "./chunks/LpHintLayer-C2asBIfs.js";
|
|
|
114
114
|
import { _ as _81 } from "./chunks/LpHints-CNZQ76JV.js";
|
|
115
115
|
import { HINT_SIGNALS, contextMenuTip, createHintSystem, dropzoneTip, hintKey, hintSummary, hotkeyTip, localHintStorage, noteDropzone, noteHotkeyTarget, paletteTip, provideHintSystem, runningHintSystem, useHintSystem } from "./composables/useHintSystem.js";
|
|
116
116
|
import { _ as _82 } from "./chunks/LpTooltipProvider-CftjV7Xc.js";
|
|
117
|
-
import { _ as _83 } from "./chunks/LpTopologyCanvas-
|
|
118
|
-
import { _ as _84 } from "./chunks/LpUptimeBar-
|
|
117
|
+
import { _ as _83 } from "./chunks/LpTopologyCanvas-Cq-MiJwo.js";
|
|
118
|
+
import { _ as _84 } from "./chunks/LpUptimeBar-BbhVTPnL.js";
|
|
119
119
|
import { deserializeLayout, serializeLayout } from "./layout/tree.js";
|
|
120
120
|
import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, removeBlock, removeLeaf, reorderTab, resizeAt, setActiveTab } from "./layout/tree.js";
|
|
121
121
|
import { blockTitle, defineBlocks } from "./layout/registry.js";
|
|
@@ -123,7 +123,7 @@ import { layoutController, useLayout } from "./layout/useLayout.js";
|
|
|
123
123
|
import { a as parseTheme, s as serializeTheme } from "./chunks/useTheme-BOV_-AiY.js";
|
|
124
124
|
import { D, T, b as b2, c as c2, d, e, f, g, l, h, n, i as i2, j, p, r, k, t, m, o, q, u, v } from "./chunks/useTheme-BOV_-AiY.js";
|
|
125
125
|
import { G, N, d as d2, i as i3, s } from "./chunks/backdrop-Dkf2vM81.js";
|
|
126
|
-
import { _ as _85, a as a2, b as b3, c as c3, d as d3, e as e2, u as u2, w } from "./chunks/LpThemeStudio-
|
|
126
|
+
import { _ as _85, a as a2, b as b3, c as c3, d as d3, S, e as e2, u as u2, w } from "./chunks/LpThemeStudio-IupHqP0x.js";
|
|
127
127
|
import { useClipboard } from "./composables/useClipboard.js";
|
|
128
128
|
import { activeShortcuts, appleModifierLabel, findHotkeyConflicts, hotkeyConflicts, hotkeySteps, isAppleKeyboard, matchesHotkey, mergeShortcutGroups, parseCombo, pendingHotkey, sequenceProgress, useAppleKeyboard, useHotkeys } from "./composables/useHotkeys.js";
|
|
129
129
|
import { anyModalOpen, topModalElement, useModalLayer } from "./components/modalDepth.js";
|
|
@@ -164,8 +164,8 @@ import { _ as _87 } from "./chunks/LpFrame-BVQCQLDI.js";
|
|
|
164
164
|
import { _ as _88 } from "./chunks/LpThemeStudioSection-BwhqYDLf.js";
|
|
165
165
|
import { isInternalHref, resolveLink } from "./components/link.js";
|
|
166
166
|
import { _ as _89 } from "./chunks/LpRepeater-OvzByE5L.js";
|
|
167
|
-
import { _ as _90 } from "./chunks/LpGauge-
|
|
168
|
-
import { _ as _91 } from "./chunks/LpQuota-
|
|
167
|
+
import { _ as _90 } from "./chunks/LpGauge-DFdt33x8.js";
|
|
168
|
+
import { _ as _91 } from "./chunks/LpQuota-Cz9PvklM.js";
|
|
169
169
|
import { _ as _92 } from "./chunks/LpStatus-BQU1TUsv.js";
|
|
170
170
|
import { _ as _93 } from "./chunks/LpRelativeTime-UM8EkfrN.js";
|
|
171
171
|
import { _ as _94 } from "./chunks/LpActivityLine-CJvzrNpj.js";
|
|
@@ -181,14 +181,14 @@ import { childProgress, connectorFilled, currentStepIndex, isSettled, overallSta
|
|
|
181
181
|
import { _ as _97 } from "./chunks/LpLoader-DjJEX8hL.js";
|
|
182
182
|
import { useLoadingGate } from "./composables/useLoadingGate.js";
|
|
183
183
|
import { useNow } from "./composables/useNow.js";
|
|
184
|
-
import { _ as _98 } from "./chunks/LpCountdown-
|
|
184
|
+
import { _ as _98 } from "./chunks/LpCountdown-CXrEK0ev.js";
|
|
185
185
|
import { DEFAULT_MILESTONES, DEFAULT_UNIT_LABELS, ceilToSecond, crossedMilestone, formatElapsed, formatTime, msToNextSecond, splitTime, timeParts } from "./components/timeSpan.js";
|
|
186
186
|
import { _ as _99 } from "./chunks/LpCopyButton-Dmq7DmFw.js";
|
|
187
187
|
import { _ as _100 } from "./chunks/LpSecretField-DwQiPOko.js";
|
|
188
188
|
import { _ as _101 } from "./chunks/LpDataList-CFPtSsBB.js";
|
|
189
189
|
import { _ as _102 } from "./chunks/LpEditable-CjxuOVh_.js";
|
|
190
190
|
import { _ as _103 } from "./chunks/LpTruncate-TcqfkyJM.js";
|
|
191
|
-
import { _ as _104 } from "./chunks/LpCodeDisplay-
|
|
191
|
+
import { _ as _104 } from "./chunks/LpCodeDisplay-C9pIMF2R.js";
|
|
192
192
|
import { formatRemaining, groupCode, middleTruncate, normalizeCode } from "./components/fieldText.js";
|
|
193
193
|
import { _ as _105 } from "./chunks/LpHoverCard-spGxyw7T.js";
|
|
194
194
|
import { _ as _106 } from "./chunks/LpIdenticon-BzdC_qz9.js";
|
|
@@ -214,10 +214,10 @@ import { _ as _115 } from "./chunks/LpSpotlight-4F3NeUMY.js";
|
|
|
214
214
|
import { _ as _116 } from "./chunks/LpGlowBorder-B8V-QzIl.js";
|
|
215
215
|
import { _ as _117 } from "./chunks/LpMagnetic-CtXKLa2Y.js";
|
|
216
216
|
import { spotlightPoint, useSpotlight } from "./composables/useSpotlight.js";
|
|
217
|
-
import { _ as _118 } from "./chunks/LpCommandScope-
|
|
217
|
+
import { _ as _118 } from "./chunks/LpCommandScope-BlKcgBnK.js";
|
|
218
218
|
import { createPaletteRegistry, mergePaletteCommands, usePaletteCommands, vPalette } from "./composables/usePaletteCommands.js";
|
|
219
|
-
import { u as u3 } from "./chunks/palette-
|
|
220
|
-
import { _ as _119 } from "./chunks/LpPaletteTheme-
|
|
219
|
+
import { u as u3 } from "./chunks/palette-Cvxl_1tM.js";
|
|
220
|
+
import { _ as _119 } from "./chunks/LpPaletteTheme-DkikZ362.js";
|
|
221
221
|
const UI_CONFIG_VERSION = 1;
|
|
222
222
|
function serializeConfig(config) {
|
|
223
223
|
const out = {
|
|
@@ -391,6 +391,7 @@ export {
|
|
|
391
391
|
b3 as STUDIO_CORNERS,
|
|
392
392
|
c3 as STUDIO_MOTION,
|
|
393
393
|
d3 as STUDIO_SIZES,
|
|
394
|
+
S as STUDIO_SYSTEM,
|
|
394
395
|
T as THEME_COOKIE,
|
|
395
396
|
TONES,
|
|
396
397
|
TONE_ALIASES,
|
package/dist/theme/palette.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type MaybeRefOrGetter } from "vue";
|
|
2
2
|
import { type PaletteScope } from "../composables/usePaletteCommands";
|
|
3
3
|
import type { ThemeStudio } from "./studio";
|
|
4
|
+
import type { ThemeStudioPart } from "../components/LpThemeStudio.vue";
|
|
4
5
|
/** Registers the studio itself; no second list of theme options in the app. */
|
|
6
|
+
/** `hide` leaves out the same studio rows the app hides in its own studio. */
|
|
5
7
|
export declare function usePaletteTheme(studio: MaybeRefOrGetter<ThemeStudio | undefined>, options?: {
|
|
6
8
|
id?: string;
|
|
7
9
|
label?: string;
|
|
8
10
|
scope?: PaletteScope;
|
|
11
|
+
hide?: ThemeStudioPart[];
|
|
9
12
|
}): void;
|
package/package.json
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
pointCount,
|
|
42
42
|
resolveLabels,
|
|
43
43
|
xAt,
|
|
44
|
+
xLabelIndices,
|
|
44
45
|
yAt,
|
|
45
46
|
type Box,
|
|
46
47
|
type ChartMarker,
|
|
@@ -255,6 +256,23 @@ const allSeries = computed(() =>
|
|
|
255
256
|
)
|
|
256
257
|
const visible = computed(() => allSeries.value.filter((s) => !hidden.value.has(s.id)))
|
|
257
258
|
|
|
259
|
+
// A series asked to start hidden is hidden once, when it first appears: the
|
|
260
|
+
// legend owns it from then on, and a data refresh must not switch it back off.
|
|
261
|
+
const seeded = new Set<string>()
|
|
262
|
+
watch(
|
|
263
|
+
() => props.series.map((s, i) => [`${i}:${s.name}`, !!s.hidden] as const),
|
|
264
|
+
(entries) => {
|
|
265
|
+
let next: Set<string> | null = null
|
|
266
|
+
for (const [id, off] of entries) {
|
|
267
|
+
if (seeded.has(id)) continue
|
|
268
|
+
seeded.add(id)
|
|
269
|
+
if (off) (next ??= new Set(hidden.value)).add(id)
|
|
270
|
+
}
|
|
271
|
+
if (next && next.size < entries.length) hidden.value = next
|
|
272
|
+
},
|
|
273
|
+
{ immediate: true },
|
|
274
|
+
)
|
|
275
|
+
|
|
258
276
|
/*
|
|
259
277
|
* How present each series is, 1 to 0, driven per frame rather than by CSS.
|
|
260
278
|
*
|
|
@@ -664,9 +682,11 @@ const xLabelStride = computed(() => {
|
|
|
664
682
|
})
|
|
665
683
|
|
|
666
684
|
const xLabels = computed(() =>
|
|
667
|
-
labels.value
|
|
668
|
-
|
|
669
|
-
|
|
685
|
+
xLabelIndices(labels.value.length, xLabelStride.value).map((i) => ({
|
|
686
|
+
label: labels.value[i],
|
|
687
|
+
i,
|
|
688
|
+
x: xAt(i, count.value, box.value, banded.value),
|
|
689
|
+
})),
|
|
670
690
|
)
|
|
671
691
|
|
|
672
692
|
// ── marks ──────────────────────────────────────
|
|
@@ -1519,7 +1519,10 @@ const guardOutside = useOutsideGuard()
|
|
|
1519
1519
|
<div class="flex gap-2"><LpButton :loading="executing || validating" @click="submitPrompt">{{ text.submit }}</LpButton><LpButton variant="ghost" @click="promptCommand = undefined; inputRef?.focus()">{{ text.backToResults }}</LpButton></div>
|
|
1520
1520
|
</div>
|
|
1521
1521
|
<p v-if="actionError" role="alert" class="px-4 py-2 text-sm text-danger">{{ actionError }}</p>
|
|
1522
|
-
|
|
1522
|
+
<!-- Touch only: there the footer with its keys is hidden, and these are
|
|
1523
|
+
the only way to the row's actions and pin. On a pointer the same
|
|
1524
|
+
buttons live in the footer, so the list never shifts under this strip. -->
|
|
1525
|
+
<div v-if="active && (canPin || selectedActions.length) && !promptCommand" class="hidden items-center justify-end gap-2 border-b border-line px-3 py-1 [@media(hover:none)_and_(pointer:coarse)]:flex">
|
|
1523
1526
|
<LpButton v-if="selectedActions.length" size="xs" variant="ghost" @click="openActions(active)">{{ text.actions }} <span class="text-muted">Tab</span></LpButton>
|
|
1524
1527
|
<LpButton v-if="canPin" size="xs" variant="ghost" :aria-label="isPinned(active) ? text.unpin : text.pin" :aria-pressed="isPinned(active)" @click="togglePin(active)"><LpIcon name="lucide:star" :size="14" />{{ isPinned(active) ? text.unpin : text.pin }} <span class="text-muted">Alt+P</span></LpButton>
|
|
1525
1528
|
</div>
|
|
@@ -1727,17 +1730,32 @@ const guardOutside = useOutsideGuard()
|
|
|
1727
1730
|
</span>
|
|
1728
1731
|
<!-- Only inside a mode: at the root Backspace just edits the query,
|
|
1729
1732
|
and advertising it there would be a lie. -->
|
|
1730
|
-
<
|
|
1731
|
-
v-if="selectedActions.length"
|
|
1732
|
-
|
|
1733
|
+
<button
|
|
1734
|
+
v-if="selectedActions.length && active"
|
|
1735
|
+
type="button"
|
|
1736
|
+
class="flex items-center gap-1 rounded-small outline-none transition-colors hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
1733
1737
|
:class="showActionsKey && 'font-medium text-brand'"
|
|
1738
|
+
:aria-label="text.actions"
|
|
1739
|
+
@click="openActions(active)"
|
|
1734
1740
|
>
|
|
1735
1741
|
<kbd
|
|
1736
1742
|
class="rounded-small border px-1 text-[10px] transition-colors"
|
|
1737
1743
|
:class="showActionsKey ? 'border-brand' : 'border-line'"
|
|
1738
1744
|
>{{ text.tab }}</kbd>
|
|
1739
1745
|
{{ text.actions }}
|
|
1740
|
-
</
|
|
1746
|
+
</button>
|
|
1747
|
+
<button
|
|
1748
|
+
v-if="canPin && active && !promptCommand"
|
|
1749
|
+
type="button"
|
|
1750
|
+
class="flex items-center gap-1 rounded-small outline-none transition-colors hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
1751
|
+
:aria-label="isPinned(active) ? text.unpin : text.pin"
|
|
1752
|
+
:aria-pressed="isPinned(active)"
|
|
1753
|
+
@click="togglePin(active)"
|
|
1754
|
+
>
|
|
1755
|
+
<kbd class="rounded-small border border-line px-1 text-[10px]">Alt+P</kbd>
|
|
1756
|
+
<LpIcon name="lucide:star" :size="11" :class="isPinned(active) && 'fill-current text-brand'" />
|
|
1757
|
+
{{ isPinned(active) ? text.unpin : text.pin }}
|
|
1758
|
+
</button>
|
|
1741
1759
|
<span v-if="trail.length" class="flex items-center gap-1">
|
|
1742
1760
|
<kbd class="rounded-small border border-line px-1 text-[10px]">⌫</kbd>
|
|
1743
1761
|
{{ text.back }}
|
|
@@ -229,8 +229,8 @@ onBeforeUnmount(stop)
|
|
|
229
229
|
* own, the same rule the dates follow: a Russian app read "4,594" otherwise.
|
|
230
230
|
*/
|
|
231
231
|
const locale = useLocale(() => props.locale)
|
|
232
|
-
//
|
|
233
|
-
const fractionMark = computed(() => (props.grouped
|
|
232
|
+
// The grouped and compact forms are written in the locale; the plain one always uses ".".
|
|
233
|
+
const fractionMark = computed(() => ((props.grouped || props.compact) && !props.format ? decimalMark(locale.value) : "."))
|
|
234
234
|
|
|
235
235
|
const formatted = computed(() => formatAt(round(displayed.value), !settled.value))
|
|
236
236
|
|
|
@@ -249,10 +249,10 @@ function formatAt(v: number, padded: boolean): string {
|
|
|
249
249
|
*
|
|
250
250
|
* The decimal count is what stays fixed: one place whenever a unit is in
|
|
251
251
|
* play, so the row's shape only changes at a rollover, not on every frame.
|
|
252
|
-
*
|
|
253
|
-
* locale's
|
|
252
|
+
* Written in the app's locale: the cells find the fraction by that
|
|
253
|
+
* locale's own mark (fractionMark), so "1,2 тыс." keeps its columns.
|
|
254
254
|
*/
|
|
255
|
-
return compactNumber(v, { plainDecimals: 0, fixed: true })
|
|
255
|
+
return compactNumber(v, { plainDecimals: 0, fixed: true, locale: locale.value, words: true })
|
|
256
256
|
}
|
|
257
257
|
const pad = padded ? Math.max(1, padTo.value) : 1
|
|
258
258
|
if (props.grouped)
|
|
@@ -280,6 +280,8 @@ interface Cell {
|
|
|
280
280
|
char: string
|
|
281
281
|
/** A leading zero held open for a column the value hasn't reached yet. */
|
|
282
282
|
isPad: boolean
|
|
283
|
+
/** A space between two digits: the locale's thousands separator. */
|
|
284
|
+
isGap: boolean
|
|
283
285
|
}
|
|
284
286
|
|
|
285
287
|
/*
|
|
@@ -338,7 +340,8 @@ const cells = computed<Cell[]>(() => {
|
|
|
338
340
|
const digit = isDigit(char) ? char.charCodeAt(0) - 48 : null
|
|
339
341
|
const isPad = leading && digit === 0 && i < intEnd - 1
|
|
340
342
|
if (digit !== null && digit !== 0) leading = false
|
|
341
|
-
|
|
343
|
+
const isGap = digit === null && /\s/.test(char) && isDigit(chars[i - 1] ?? "") && isDigit(chars[i + 1] ?? "")
|
|
344
|
+
return { key: keys[i], digit, char, isPad, isGap }
|
|
342
345
|
})
|
|
343
346
|
})
|
|
344
347
|
|
|
@@ -380,7 +383,7 @@ const label = computed(
|
|
|
380
383
|
v-for="cell in cells"
|
|
381
384
|
:key="cell.key"
|
|
382
385
|
class="lp-nf__cell"
|
|
383
|
-
:class="cell.isPad
|
|
386
|
+
:class="{ 'lp-nf__cell--pad': cell.isPad, 'lp-nf__cell--gap': cell.isGap }"
|
|
384
387
|
>
|
|
385
388
|
<!-- A digit is a window onto its strip; a separator is just a glyph. -->
|
|
386
389
|
<template v-if="cell.digit !== null">
|
|
@@ -486,6 +489,15 @@ const label = computed(
|
|
|
486
489
|
|
|
487
490
|
/* Faded, so a held-open column reads as an empty wheel rather than a zero the
|
|
488
491
|
number actually has. */
|
|
492
|
+
/*
|
|
493
|
+
* A space separator (ru, fr, …) takes a whole column in a monospaced or
|
|
494
|
+
* tabular face, so "7 271" read as "7 271". It is drawn as the narrow gap
|
|
495
|
+
* the locale means.
|
|
496
|
+
*/
|
|
497
|
+
.lp-nf__cell--gap {
|
|
498
|
+
width: 0.25em;
|
|
499
|
+
}
|
|
500
|
+
|
|
489
501
|
.lp-nf__cell--pad {
|
|
490
502
|
opacity: 0.18;
|
|
491
503
|
transition: opacity var(--duration-fast, 160ms) linear;
|
|
@@ -3,12 +3,12 @@ import { computed, ref } from "vue"
|
|
|
3
3
|
import type { StudioState, PreviewableThemeStudio } from "../theme/studio"
|
|
4
4
|
import { useLabels } from "../composables/useLpConfig"
|
|
5
5
|
import { suppressPaletteDiscovery } from "../composables/usePaletteCommands"
|
|
6
|
-
import LpThemeStudio from "./LpThemeStudio.vue"
|
|
6
|
+
import LpThemeStudio, { type ThemeStudioPart } from "./LpThemeStudio.vue"
|
|
7
7
|
import LpThemeProvider from "./LpThemeProvider.vue"
|
|
8
8
|
import LpButton from "./LpButton.vue"
|
|
9
9
|
import LpInput from "./LpInput.vue"
|
|
10
10
|
|
|
11
|
-
const props = defineProps<{ studio: PreviewableThemeStudio }>()
|
|
11
|
+
const props = defineProps<{ studio: PreviewableThemeStudio; hide?: ThemeStudioPart[] }>()
|
|
12
12
|
suppressPaletteDiscovery()
|
|
13
13
|
const text = useLabels("paletteTheme", { title: "Theme", apply: "Apply theme", revert: "Revert draft", sample: "Preview", action: "Continue", placeholder: "A field in this theme" })
|
|
14
14
|
const snapshot = ref({ ...props.studio.state.value })
|
|
@@ -43,6 +43,6 @@ function apply() {
|
|
|
43
43
|
<LpInput :placeholder="text.placeholder" readonly :tabindex="-1" />
|
|
44
44
|
<LpButton :tabindex="-1">{{ text.action }}</LpButton>
|
|
45
45
|
</LpThemeProvider>
|
|
46
|
-
<LpThemeStudio :studio="draft" :labels="{ reset: text.revert }" />
|
|
46
|
+
<LpThemeStudio :studio="draft" :hide="hide" :labels="{ reset: text.revert }" />
|
|
47
47
|
</div>
|
|
48
48
|
</template>
|
|
@@ -104,10 +104,11 @@ const props = withDefaults(
|
|
|
104
104
|
},
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
+
/* The text steps with the height, as in every other kit field: a small select beside a small input read a size larger. */
|
|
107
108
|
const SIZE_HEIGHT = {
|
|
108
|
-
sm: "h-(--size-control-sm)",
|
|
109
|
-
md: "h-(--size-control-md)",
|
|
110
|
-
lg: "h-(--size-control-lg)",
|
|
109
|
+
sm: "h-(--size-control-sm) text-xs",
|
|
110
|
+
md: "h-(--size-control-md) text-sm",
|
|
111
|
+
lg: "h-(--size-control-lg) text-sm",
|
|
111
112
|
} as const
|
|
112
113
|
const anchorHeight = computed(() => SIZE_HEIGHT[props.size])
|
|
113
114
|
|
|
@@ -431,7 +432,7 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
|
|
|
431
432
|
@focusout="onFocusOut"
|
|
432
433
|
:class="[
|
|
433
434
|
anchorHeight,
|
|
434
|
-
'group flex w-full items-center gap-2 rounded-control border bg-surface-soft px-3 text-
|
|
435
|
+
'group flex w-full items-center gap-2 rounded-control border bg-surface-soft px-3 text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2',
|
|
435
436
|
field.disabled && 'cursor-not-allowed opacity-55',
|
|
436
437
|
field.invalid
|
|
437
438
|
? 'border-danger focus-within:border-danger focus-within:ring-danger-soft'
|
|
@@ -15,6 +15,7 @@ export default { inheritAttrs: false }
|
|
|
15
15
|
import { Motion } from "motion-v"
|
|
16
16
|
import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from "reka-ui"
|
|
17
17
|
import { computed, ref } from "vue"
|
|
18
|
+
import { useLocale } from "../composables/useLocale"
|
|
18
19
|
import { usePillTransition } from "../composables/usePillTransition"
|
|
19
20
|
import LpBadge from "./LpBadge.vue"
|
|
20
21
|
import LpContextMenu from "./LpContextMenu.vue"
|
|
@@ -61,6 +62,12 @@ const emit = defineEmits<{
|
|
|
61
62
|
}>()
|
|
62
63
|
|
|
63
64
|
/** `key` when the app set one, else the title — see SidebarSection.key. */
|
|
65
|
+
// A count reads in the app's locale like every other figure: "7 563", not "7563".
|
|
66
|
+
const locale = useLocale()
|
|
67
|
+
function badgeText(badge: number | string): string {
|
|
68
|
+
return typeof badge === "number" ? badge.toLocaleString(locale.value) : badge
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
function sectionKey(section: SidebarSection): string {
|
|
65
72
|
return section.key ?? section.title ?? ""
|
|
66
73
|
}
|
|
@@ -245,7 +252,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
245
252
|
:tone="itemActive(item) ? 'brand' : 'neutral'"
|
|
246
253
|
class="relative z-10 shrink-0"
|
|
247
254
|
>
|
|
248
|
-
{{ item.badge }}
|
|
255
|
+
{{ badgeText(item.badge) }}
|
|
249
256
|
</LpBadge>
|
|
250
257
|
</slot>
|
|
251
258
|
</component>
|
|
@@ -141,10 +141,12 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
141
141
|
</Transition>
|
|
142
142
|
</span>
|
|
143
143
|
</div>
|
|
144
|
-
|
|
144
|
+
<!-- A narrow tile moves the delta under the figure rather than breaking the
|
|
145
|
+
figure itself: "2085 ч" split across two lines reads as two values. -->
|
|
146
|
+
<div class="flex flex-wrap items-baseline gap-x-2">
|
|
145
147
|
<!-- The placeholder sits INSIDE the value's own type size, so its line box
|
|
146
148
|
is the real one: the tile cannot change height when the figure lands. -->
|
|
147
|
-
<span class="font-mono text-3xl font-medium tracking-tight text-ink">
|
|
149
|
+
<span class="whitespace-nowrap font-mono text-3xl font-medium tracking-tight text-ink">
|
|
148
150
|
<!-- Each side wrapped in a plain element: LpNumberFlow renders more
|
|
149
151
|
than one root node, which a Transition cannot fade. -->
|
|
150
152
|
<Transition v-bind="FADE">
|
|
@@ -173,7 +175,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
173
175
|
</span>
|
|
174
176
|
<span
|
|
175
177
|
v-else-if="showDelta"
|
|
176
|
-
class="inline-flex items-center gap-0.5 text-sm font-medium tabular-nums"
|
|
178
|
+
class="inline-flex items-center gap-0.5 whitespace-nowrap text-sm font-medium tabular-nums"
|
|
177
179
|
:class="trendColor"
|
|
178
180
|
>
|
|
179
181
|
<LpIcon :name="trendIcon" :size="14" />
|
|
@@ -61,8 +61,15 @@ const props = withDefaults(
|
|
|
61
61
|
* where the pointer crosses the bar on its way elsewhere.
|
|
62
62
|
*/
|
|
63
63
|
hoverFollow?: boolean
|
|
64
|
+
/**
|
|
65
|
+
* Icons only, the label kept for screen readers and shown on hover. For a
|
|
66
|
+
* header that cannot fit every label: a tab cut off at the edge, or hidden
|
|
67
|
+
* under the actions beside it, cannot be reached at all. Tabs without an
|
|
68
|
+
* icon keep their label.
|
|
69
|
+
*/
|
|
70
|
+
compact?: boolean
|
|
64
71
|
}>(),
|
|
65
|
-
{ variant: "contained", accent: false, block: false, hoverFollow: true },
|
|
72
|
+
{ variant: "contained", accent: false, block: false, hoverFollow: true, compact: false },
|
|
66
73
|
)
|
|
67
74
|
|
|
68
75
|
const bottom = computed(() => props.variant === "bottom")
|
|
@@ -190,6 +197,7 @@ usePaletteCommands(() => props.items.map(item => ({
|
|
|
190
197
|
<TabsTrigger
|
|
191
198
|
:value="item.value"
|
|
192
199
|
:disabled="item.disabled"
|
|
200
|
+
:title="compact && item.icon && !bottom ? item.label : undefined"
|
|
193
201
|
class="relative inline-flex items-center justify-center outline-none transition-[color,opacity] duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-45"
|
|
194
202
|
:class="[
|
|
195
203
|
bottom
|
|
@@ -244,7 +252,7 @@ usePaletteCommands(() => props.items.map(item => ({
|
|
|
244
252
|
</LpBadge>
|
|
245
253
|
</Transition>
|
|
246
254
|
</span>
|
|
247
|
-
<span class="relative z-10" :class="bottom ? 'max-w-full truncate' : ''">{{ item.label }}</span>
|
|
255
|
+
<span class="relative z-10" :class="bottom ? 'max-w-full truncate' : compact && item.icon ? 'sr-only' : ''">{{ item.label }}</span>
|
|
248
256
|
<AttentionDot v-if="item.dot && !item.icon" />
|
|
249
257
|
<!-- The count rolls when it changes, and the chip scales in and out
|
|
250
258
|
rather than popping. -->
|
|
@@ -105,7 +105,7 @@ const SWATCH = "relative size-8 overflow-hidden rounded-full ring-2 transition-[
|
|
|
105
105
|
|
|
106
106
|
const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K, value: string) => studio.tune({ [key]: value } as Partial<StudioState>)
|
|
107
107
|
|
|
108
|
-
usePaletteTheme(() => studio ?? undefined)
|
|
108
|
+
usePaletteTheme(() => studio ?? undefined, { hide: props.hide })
|
|
109
109
|
</script>
|
|
110
110
|
|
|
111
111
|
<template>
|
|
@@ -228,6 +228,9 @@ function followSystem() {
|
|
|
228
228
|
// With the studio, "System" is its base theme, so the tuning stays on top.
|
|
229
229
|
if (tuner) {
|
|
230
230
|
tuner.pickTheme(STUDIO_SYSTEM)
|
|
231
|
+
// A v-model bound to the switcher learns "system" too, or it would keep
|
|
232
|
+
// the previous name and win over the studio: the check stayed on it.
|
|
233
|
+
emit("update:modelValue", STUDIO_SYSTEM)
|
|
231
234
|
emit("change", tuner.theme.value)
|
|
232
235
|
return
|
|
233
236
|
}
|
|
@@ -300,7 +303,7 @@ const triggerLabel = computed(
|
|
|
300
303
|
() => props["aria-label"] ?? text.value.trigger.replace("{name}", labelName.value),
|
|
301
304
|
)
|
|
302
305
|
|
|
303
|
-
usePaletteTheme(() => tuner ?? undefined)
|
|
306
|
+
usePaletteTheme(() => tuner ?? undefined, { hide: props.studioHide })
|
|
304
307
|
</script>
|
|
305
308
|
|
|
306
309
|
<template>
|
|
@@ -20,9 +20,10 @@ import {
|
|
|
20
20
|
type Connection,
|
|
21
21
|
type Node,
|
|
22
22
|
type NodeTypesObject,
|
|
23
|
+
type Padding,
|
|
23
24
|
} from "@vue-flow/core"
|
|
24
25
|
import { MiniMap } from "@vue-flow/minimap"
|
|
25
|
-
import { computed, defineComponent, h, markRaw, watch, type Component } from "vue"
|
|
26
|
+
import { computed, defineComponent, h, markRaw, ref, watch, type Component } from "vue"
|
|
26
27
|
import LpInfraNode, { type InfraNodeData } from "./LpInfraNode.vue"
|
|
27
28
|
import LpLaneNode from "./LpLaneNode.vue"
|
|
28
29
|
import LpServiceNode, { type ServiceNodeData } from "./LpServiceNode.vue"
|
|
@@ -325,6 +326,7 @@ const {
|
|
|
325
326
|
getSelectedNodes,
|
|
326
327
|
fitView,
|
|
327
328
|
setViewport,
|
|
329
|
+
onNodesInitialized,
|
|
328
330
|
} = useVueFlow()
|
|
329
331
|
onNodeClick(({ node }) => emit("node-select", node.id))
|
|
330
332
|
// A node usually has two levels of interest — pick it out, or open it — and a
|
|
@@ -389,13 +391,42 @@ onEdgeContextMenu(({ edge, event }) => {
|
|
|
389
391
|
onPaneClick(() => emit("pane-click"))
|
|
390
392
|
onConnect((conn) => emit("connect", conn))
|
|
391
393
|
|
|
394
|
+
// The legend and the minimap float over the canvas, so a plain fit puts the
|
|
395
|
+
// outermost nodes under them. Fitting reserves their corner instead: the side
|
|
396
|
+
// they sit on gets their height on top of the usual margin. On a phone both
|
|
397
|
+
// are hidden and only the margin is left.
|
|
398
|
+
const legendBox = ref<HTMLElement | null>(null)
|
|
399
|
+
const MINIMAP_HEIGHT = 165
|
|
400
|
+
const FRAME = 24
|
|
401
|
+
function framePadding(): Padding {
|
|
402
|
+
const wide = typeof window === "undefined" || window.matchMedia("(min-width: 640px)").matches
|
|
403
|
+
const legendHeight = wide && props.legend ? (legendBox.value?.offsetHeight ?? 0) + 15 : 0
|
|
404
|
+
const legendTop = props.legendPosition.startsWith("top")
|
|
405
|
+
const bottom = Math.max(legendTop ? 0 : legendHeight, wide && props.minimap ? MINIMAP_HEIGHT : 0)
|
|
406
|
+
return {
|
|
407
|
+
top: `${FRAME + (legendTop ? legendHeight : 0)}px`,
|
|
408
|
+
bottom: `${FRAME + bottom}px`,
|
|
409
|
+
left: `${FRAME}px`,
|
|
410
|
+
right: `${FRAME}px`,
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function fitFramed(params: Parameters<typeof fitView>[0] = {}) {
|
|
414
|
+
return fitView({ padding: framePadding(), ...params })
|
|
415
|
+
}
|
|
416
|
+
let fitted = false
|
|
417
|
+
onNodesInitialized(() => {
|
|
418
|
+
if (fitted) return
|
|
419
|
+
fitted = true
|
|
420
|
+
void fitFramed()
|
|
421
|
+
})
|
|
422
|
+
|
|
392
423
|
// Restore a saved viewport (pan/zoom). The host calls this after loading the
|
|
393
424
|
// user's persisted CanvasViewport on mount.
|
|
394
425
|
async function applyViewport(v: CanvasViewport): Promise<void> {
|
|
395
426
|
await setViewport({ x: v.x, y: v.y, zoom: v.zoom })
|
|
396
427
|
}
|
|
397
428
|
|
|
398
|
-
defineExpose({ fitView, setViewport: applyViewport })
|
|
429
|
+
defineExpose({ fitView: fitFramed, setViewport: applyViewport })
|
|
399
430
|
</script>
|
|
400
431
|
|
|
401
432
|
<template>
|
|
@@ -413,7 +444,6 @@ defineExpose({ fitView, setViewport: applyViewport })
|
|
|
413
444
|
:default-viewport="{ zoom: 0.9 }"
|
|
414
445
|
:min-zoom="0.3"
|
|
415
446
|
:max-zoom="2"
|
|
416
|
-
fit-view-on-init
|
|
417
447
|
class="lp-topology"
|
|
418
448
|
>
|
|
419
449
|
<Background :gap="22" :size="1.4" pattern-color="var(--color-line-strong)" />
|
|
@@ -428,13 +458,14 @@ defineExpose({ fitView, setViewport: applyViewport })
|
|
|
428
458
|
node-color="var(--color-surface-soft)"
|
|
429
459
|
mask-color="rgba(0,0,0,0.55)"
|
|
430
460
|
/>
|
|
431
|
-
<Controls v-if="controls" />
|
|
461
|
+
<Controls v-if="controls" :fit-view-params="{ padding: framePadding() }" />
|
|
432
462
|
|
|
433
463
|
<!-- Edge-type legend: makes the colour/dash encoding self-explanatory. -->
|
|
434
464
|
<!-- On a phone the legend would sit on the graph itself; the edge styles
|
|
435
465
|
stay readable without it there. -->
|
|
436
466
|
<Panel v-if="legend" :position="legendPosition" :class="legendOffset" class="max-sm:hidden">
|
|
437
467
|
<div
|
|
468
|
+
ref="legendBox"
|
|
438
469
|
class="flex flex-col gap-1.5 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-[11px] backdrop-blur"
|
|
439
470
|
>
|
|
440
471
|
<div
|
package/src/components/chart.ts
CHANGED
|
@@ -28,6 +28,13 @@ export interface ChartSeries {
|
|
|
28
28
|
* own scale and its own ticks.
|
|
29
29
|
*/
|
|
30
30
|
axis?: "left" | "right"
|
|
31
|
+
/**
|
|
32
|
+
* Start with this series switched off, the legend turning it back on. For a
|
|
33
|
+
* series worth having but loud next to the rest: messages in thousands beside
|
|
34
|
+
* people in tens flatten the people line on a shared axis until they are
|
|
35
|
+
* hidden, so the chart opens on the readable view.
|
|
36
|
+
*/
|
|
37
|
+
hidden?: boolean
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
export type ChartAxis = "left" | "right"
|
|
@@ -58,6 +65,25 @@ export interface ChartMarker {
|
|
|
58
65
|
color?: string
|
|
59
66
|
}
|
|
60
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Which x labels to print with every `stride`-th one shown. The last point is
|
|
70
|
+
* always labelled (it is usually "now"), and when it lands within half a
|
|
71
|
+
* stride to the previous shown label that one gives way: "20 22 23" read as
|
|
72
|
+
* one smudge at the end of an hourly axis.
|
|
73
|
+
*/
|
|
74
|
+
export function xLabelIndices(count: number, stride: number): number[] {
|
|
75
|
+
if (count <= 0) return []
|
|
76
|
+
const step = Math.max(1, Math.floor(stride))
|
|
77
|
+
const out: number[] = []
|
|
78
|
+
for (let i = 0; i < count; i += step) out.push(i)
|
|
79
|
+
const last = count - 1
|
|
80
|
+
if (out[out.length - 1] !== last) {
|
|
81
|
+
if (out.length > 1 && last - out[out.length - 1] <= step / 2) out.pop()
|
|
82
|
+
out.push(last)
|
|
83
|
+
}
|
|
84
|
+
return out
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
/** The x index a marker points at, or -1 when it names nothing on the axis. */
|
|
62
88
|
export const markerIndex = (at: number | string, labels: string[], count: number): number => {
|
|
63
89
|
if (typeof at === "number") return Number.isInteger(at) && at >= 0 && at < count ? at : -1
|