@miragon/modeler-shell 0.0.0-stage → 0.1.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/API.md +1902 -0
- package/LICENSE +21 -0
- package/README.md +435 -2
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/appearance-D3FUcbu6.d.cts +53 -0
- package/dist/appearance-D3FUcbu6.d.ts +53 -0
- package/dist/chunk-5JSZ4VGA.js +551 -0
- package/dist/chunk-5JSZ4VGA.js.map +1 -0
- package/dist/chunk-6LQOFACK.js +1576 -0
- package/dist/chunk-6LQOFACK.js.map +1 -0
- package/dist/chunk-N4QXFF3A.js +23 -0
- package/dist/chunk-N4QXFF3A.js.map +1 -0
- package/dist/chunk-RJRFR6N3.js +136 -0
- package/dist/chunk-RJRFR6N3.js.map +1 -0
- package/dist/core.cjs +619 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +171 -0
- package/dist/core.d.ts +171 -0
- package/dist/core.js +91 -0
- package/dist/core.js.map +1 -0
- package/dist/diagram-js.cjs +1115 -0
- package/dist/diagram-js.cjs.map +1 -0
- package/dist/diagram-js.d.cts +163 -0
- package/dist/diagram-js.d.ts +163 -0
- package/dist/diagram-js.js +867 -0
- package/dist/diagram-js.js.map +1 -0
- package/dist/index.cjs +2175 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +103 -0
- package/dist/index.js.map +1 -0
- package/dist/keymap-CObQE6q6.d.cts +151 -0
- package/dist/keymap-DORzHnK-.d.ts +151 -0
- package/dist/protocol-DhYBakWL.d.cts +15 -0
- package/dist/protocol-IeW8bf4f.d.ts +15 -0
- package/dist/shell.css +914 -0
- package/dist/testing-vscode-stub.cjs +332 -0
- package/dist/testing-vscode-stub.cjs.map +1 -0
- package/dist/testing-vscode-stub.d.cts +65 -0
- package/dist/testing-vscode-stub.d.ts +65 -0
- package/dist/testing-vscode-stub.js +154 -0
- package/dist/testing-vscode-stub.js.map +1 -0
- package/dist/testing.cjs +2364 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +165 -0
- package/dist/testing.d.ts +165 -0
- package/dist/testing.js +2297 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-B6NfkiUG.d.cts +66 -0
- package/dist/types-B6NfkiUG.d.ts +66 -0
- package/dist/types-DnZn9CPw.d.ts +284 -0
- package/dist/types-OuFlqZp0.d.cts +284 -0
- package/dist/vscode-extension.cjs +215 -0
- package/dist/vscode-extension.cjs.map +1 -0
- package/dist/vscode-extension.d.cts +103 -0
- package/dist/vscode-extension.d.ts +103 -0
- package/dist/vscode-extension.js +24 -0
- package/dist/vscode-extension.js.map +1 -0
- package/dist/vscode-webview.cjs +2198 -0
- package/dist/vscode-webview.cjs.map +1 -0
- package/dist/vscode-webview.d.cts +82 -0
- package/dist/vscode-webview.d.ts +82 -0
- package/dist/vscode-webview.js +250 -0
- package/dist/vscode-webview.js.map +1 -0
- package/package.json +145 -4
- package/src/core/appearance.ts +104 -0
- package/src/core/fit.ts +205 -0
- package/src/core/index.ts +84 -0
- package/src/core/keymap.ts +275 -0
- package/src/core/layout.ts +69 -0
- package/src/core/messages.ts +88 -0
- package/src/core/sizes.ts +85 -0
- package/src/core/types.ts +84 -0
- package/src/core/zoom.ts +74 -0
- package/src/core.ts +2 -0
- package/src/diagram-js/connect.ts +75 -0
- package/src/diagram-js/index.ts +16 -0
- package/src/diagram-js/module.ts +20 -0
- package/src/diagram-js/palette-skin.ts +267 -0
- package/src/diagram-js/palette.css +269 -0
- package/src/diagram-js/service.ts +240 -0
- package/src/diagram-js/types.ts +67 -0
- package/src/diagram-js/viewport.ts +564 -0
- package/src/diagram-js.ts +2 -0
- package/src/dom/element.ts +51 -0
- package/src/dom/hint.ts +51 -0
- package/src/dom/icons.ts +309 -0
- package/src/dom/index.ts +29 -0
- package/src/dom/inspector.ts +35 -0
- package/src/dom/keys.ts +59 -0
- package/src/dom/ledger.ts +87 -0
- package/src/dom/legend.ts +85 -0
- package/src/dom/menu.ts +393 -0
- package/src/dom/mount.ts +461 -0
- package/src/dom/problems.ts +72 -0
- package/src/dom/shell-dom.css +966 -0
- package/src/dom/status.ts +72 -0
- package/src/dom/tooltip.ts +237 -0
- package/src/dom/types.ts +277 -0
- package/src/dom/zoom-panel.ts +154 -0
- package/src/index.ts +3 -0
- package/src/shell.css +6 -0
- package/src/testing/cases.ts +1012 -0
- package/src/testing/checks.ts +832 -0
- package/src/testing/facts.ts +587 -0
- package/src/testing/geometry.ts +472 -0
- package/src/testing/harness.ts +138 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/page-tools.ts +434 -0
- package/src/testing/page.ts +17 -0
- package/src/testing/vscode-stub.ts +237 -0
- package/src/testing-vscode-stub.ts +2 -0
- package/src/testing.ts +2 -0
- package/src/vscode/extension/html.ts +107 -0
- package/src/vscode/extension/index.ts +8 -0
- package/src/vscode/extension/reveal.ts +9 -0
- package/src/vscode/extension/theme-bridge.ts +85 -0
- package/src/vscode/extension/types.ts +36 -0
- package/src/vscode/protocol.ts +31 -0
- package/src/vscode/webview/appearance.ts +141 -0
- package/src/vscode/webview/index.ts +8 -0
- package/src/vscode/webview/mount.ts +76 -0
- package/src/vscode/webview/state.ts +116 -0
- package/src/vscode-extension.ts +2 -0
- package/src/vscode-webview.ts +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core.ts","../src/core/types.ts","../src/core/sizes.ts","../src/core/fit.ts","../src/core/zoom.ts","../src/core/keymap.ts","../src/core/appearance.ts","../src/core/messages.ts","../src/core/layout.ts"],"sourcesContent":["// \"@miragon/modeler-shell/core\": DOM-free (Node, extension host, workers, unit tests).\nexport * from \"./core/index.js\";\n","/** A point in pixels or diagram coordinates, as the context says. */\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\n/** A rectangle; content bounds and viewboxes are in diagram coordinates. */\nexport interface Bounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** A viewbox together with the scale diagram-js shows it at (`canvas.viewbox().scale`). */\nexport interface ScaledViewbox extends Bounds {\n scale: number;\n}\n\n/** Distances in pixels from the edges of the modeler's container. */\nexport interface Insets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\n/**\n * The host column of guide §5.2: `ide` is a VS Code webview (later IntelliJ), `web` a Miragon web\n * app that owns its page, `embedded` a modeler inside a foreign page (designIQ, modeler-api hosts).\n */\nexport type ShellHost = \"ide\" | \"web\" | \"embedded\";\n\n/** The `data-chrome` names of the islands (guide §5.2, §13.4). `primary` is reserved for the web app. */\nexport type ChromeIsland =\n \"menu\" | \"palette\" | \"primary\" | \"zoom\" | \"problems\" | \"legend\" | \"inspector\" | \"status\";\n\nexport const CHROME_ISLANDS: readonly ChromeIsland[] = [\n \"menu\",\n \"palette\",\n \"primary\",\n \"zoom\",\n \"problems\",\n \"legend\",\n \"inspector\",\n \"status\",\n];\n\n/** Where the palette sits: in the top row, or in the second row below it (guide §5.15). */\nexport type PaletteRow = \"top\" | \"second-row\";\n\n/**\n * What the zoom panel and the chrome keys act on. The diagram-js viewport implements it; the DOM\n * shell only talks to this interface.\n */\nexport interface ZoomTarget {\n /** The current zoom, 1 = 100 % (diagram-js `canvas.zoom()`, rounded to three decimals). */\n zoom(): number;\n /** One step on the zoom raster around the container centre (diagram-js `zoomScroll.stepZoom`). */\n stepZoom(direction: 1 | -1): void;\n /** 100 % around the container centre. Counts as a user move. */\n zoomToActual(): void;\n /** Explicit fit (button, Shift+1, command): animated unless reduced motion, makes the view automatic. */\n fit(): void;\n /** False while there is nothing to fit (the content bounds are empty). */\n canFit(): boolean;\n /**\n * Calls `listener` after every viewbox change and whenever `canFit()` may have changed, possibly\n * several times per frame. Returns the unsubscribe function.\n */\n onChange(listener: () => void): () => void;\n}\n\n/** The persisted view (VS Code `setState`): JSON-serializable. */\nexport interface ViewportState {\n viewbox: Bounds;\n /** The user zoomed or panned since the last fit; a resize no longer refits. */\n userMoved: boolean;\n}\n","/**\n * The shell dimensions of the design system (guide §5.1, `shell` group of\n * `cd-tokens.generated.json`), keyed by a readable name. Scripts use `SHELL`; CSS uses the\n * `var(--cd-shell-*)` variables the consumer vendors. A drift test pins `SHELL` to the token JSON.\n */\nexport const SHELL_TOKENS = {\n edge: \"--cd-shell-rand\",\n gap: \"--cd-shell-luecke\",\n row: \"--cd-shell-zeile\",\n target: \"--cd-shell-ziel\",\n entry: \"--cd-shell-eintrag\",\n free: \"--cd-shell-frei\",\n menuMinWidth: \"--cd-shell-menue-breite\",\n inspectorWidth: \"--cd-shell-inspector-breite\",\n fitInsetTop: \"--cd-shell-einpassen-oben\",\n fitInsetTopSecondRow: \"--cd-shell-einpassen-oben-schmal\",\n fitInsetSide: \"--cd-shell-einpassen-seite\",\n fitInsetBottom: \"--cd-shell-einpassen-unten\",\n fitInsetBottomWeb: \"--cd-shell-einpassen-unten-web\",\n narrow: \"--cd-shell-schmal\",\n veryNarrow: \"--cd-shell-sehr-schmal\",\n low: \"--cd-shell-niedrig\",\n layerIslands: \"--cd-shell-ebene-inseln\",\n layerInspector: \"--cd-shell-ebene-inspector\",\n layerTopRow: \"--cd-shell-ebene-reihe\",\n layerPopups: \"--cd-shell-ebene-aufklapper\",\n layerDialog: \"--cd-shell-ebene-dialog\",\n layerToast: \"--cd-shell-ebene-toast\",\n} as const;\n\nexport type ShellSizeName = keyof typeof SHELL_TOKENS;\n\nexport type ShellSizes = Readonly<Record<ShellSizeName, number>>;\n\n/** Pixels (layers: plain z-index numbers), equal to `parseFloat` of the token JSON values. */\nexport const SHELL: ShellSizes = {\n edge: 12,\n gap: 8,\n row: 40,\n target: 32,\n entry: 36,\n free: 64,\n menuMinWidth: 248,\n inspectorWidth: 288,\n fitInsetTop: 88,\n fitInsetTopSecondRow: 140,\n fitInsetSide: 24,\n fitInsetBottom: 64,\n fitInsetBottomWeb: 92,\n narrow: 760,\n veryNarrow: 440,\n low: 820,\n layerIslands: 20,\n layerInspector: 25,\n layerTopRow: 30,\n layerPopups: 40,\n layerDialog: 50,\n layerToast: 60,\n};\n\n// Shell sizes are pixels (\"12px\") or plain z-index numbers (\"20\"); any other unit would make\n// parseFloat silently return a number with the wrong meaning.\nconst PIXEL_OR_PLAIN_NUMBER = /^\\s*-?(?:\\d+\\.?\\d*|\\.\\d+)(?:px)?\\s*$/;\n\n/**\n * Reads the `shell` group of `cd-tokens.generated.json` (`{ \"--cd-shell-rand\": \"12px\", … }`) into\n * numbers. Throws when a size of `SHELL_TOKENS` is missing or not a number.\n */\nexport function shellSizesFromTokens(shellGroup: Readonly<Record<string, string>>): ShellSizes {\n const sizes = {} as Record<ShellSizeName, number>;\n for (const name of Object.keys(SHELL_TOKENS) as ShellSizeName[]) {\n const token = SHELL_TOKENS[name];\n const raw: unknown = Object.hasOwn(shellGroup, token) ? shellGroup[token] : undefined;\n if (raw === undefined) {\n throw new Error(`@miragon/modeler-shell: the shell token ${token} is missing`);\n }\n if (typeof raw !== \"string\" || !PIXEL_OR_PLAIN_NUMBER.test(raw)) {\n throw new Error(\n `@miragon/modeler-shell: the shell token ${token} is not a number: ${JSON.stringify(raw)}`,\n );\n }\n sizes[name] = Number.parseFloat(raw);\n }\n return sizes;\n}\n","import { SHELL } from \"./sizes.js\";\nimport type { Bounds, Insets, PaletteRow, Point, ScaledViewbox, ShellHost, Size } from \"./types.js\";\n\n/** Fit never zooms above 100 % (guide §5.9). */\nexport const MAX_FIT_ZOOM = 1;\n\n/** Duration of the explicit, animated fit (`--cd-motion-base`). */\nexport const FIT_ANIMATION_MS = 250;\n\n/**\n * The viewbox that shows `bounds` centred in the free rectangle (`container` minus `insets`) at the\n * largest scale that fits, capped at `maxZoom`. The box has the container's aspect ratio, so\n * diagram-js's `canvas.viewbox(box)` sets exactly this view. Returns `undefined` when the container\n * is 0 × 0 or `bounds` has neither width nor height; a zero width or height scales by the other\n * dimension. Where the insets leave no free room on an axis (a container lower or narrower than\n * its chrome), that axis centres in the whole container instead.\n */\nexport function fitViewbox(\n bounds: Bounds,\n container: Size,\n insets: Insets,\n maxZoom: number = MAX_FIT_ZOOM,\n): ScaledViewbox | undefined {\n if (!(container.width > 0 && container.height > 0)) return undefined;\n const hasWidth = bounds.width > 0;\n const hasHeight = bounds.height > 0;\n if (!hasWidth && !hasHeight) return undefined;\n\n const horizontal = freeExtent(container.width, insets.left, insets.right);\n const vertical = freeExtent(container.height, insets.top, insets.bottom);\n const contentWidth = hasWidth ? bounds.width : 0;\n const contentHeight = hasHeight ? bounds.height : 0;\n const scale = Math.min(\n maxZoom,\n hasWidth ? horizontal.extent / contentWidth : Infinity,\n hasHeight ? vertical.extent / contentHeight : Infinity,\n );\n const box: ScaledViewbox = {\n x: bounds.x + contentWidth / 2 - (horizontal.start + horizontal.extent / 2) / scale,\n y: bounds.y + contentHeight / 2 - (vertical.start + vertical.extent / 2) / scale,\n width: container.width / scale,\n height: container.height / scale,\n scale,\n };\n return Object.values(box).every(Number.isFinite) && scale > 0 ? box : undefined;\n}\n\nfunction freeExtent(total: number, before: number, after: number): { start: number; extent: number } {\n const extent = total - before - after;\n return extent > 0 ? { start: before, extent } : { start: 0, extent: total };\n}\n\n/**\n * The centre of the free rectangle (`container` minus `insets`) in pixels from the container's top\n * left, where `fitViewbox` centres the content and keyboard creation places an element (guide\n * §5.5). An axis the insets leave no room on centres in the whole container, as in `fitViewbox`.\n * Returns `undefined` when the container is 0 × 0.\n */\nexport function freeRectCenter(container: Size, insets: Insets): Point | undefined {\n if (!(container.width > 0 && container.height > 0)) return undefined;\n const horizontal = freeExtent(container.width, insets.left, insets.right);\n const vertical = freeExtent(container.height, insets.top, insets.bottom);\n return {\n x: horizontal.start + horizontal.extent / 2,\n y: vertical.start + vertical.extent / 2,\n };\n}\n\n/** What is open or shown when a fit runs (guide §5.9: every fit counts what is open right now). */\nexport interface FitLayout {\n container: Size;\n host: ShellHost;\n paletteRow: PaletteRow;\n /** The problems pill is shown (only the web app reserves room for it). */\n problemsVisible: boolean;\n /** Rendered width of the open legend; `undefined` when it is closed or the modeler has none. */\n legendWidth?: number | undefined;\n /**\n * The open inspector; `undefined` when closed. Above `SHELL.narrow` it is the side panel; up to it\n * a bottom sheet whose top edge (`sheetTop`, px from the container top) must be measured.\n */\n inspector?: { sheetTop?: number | undefined } | undefined;\n}\n\n/**\n * The fit insets of guide §5.9: top 88 (second row 140), sides 24, bottom 64 (web app with pill 92),\n * right 324 with an open side inspector, left 12 + legend width + 24 with an open legend above\n * `SHELL.veryNarrow`, bottom = distance to the sheet's top edge + 12 with an open bottom sheet.\n */\nexport function fitInsets(layout: FitLayout): Insets {\n const { container, host, paletteRow, problemsVisible, legendWidth, inspector } = layout;\n const insets: Insets = {\n top: paletteRow === \"second-row\" ? SHELL.fitInsetTopSecondRow : SHELL.fitInsetTop,\n right: SHELL.fitInsetSide,\n bottom: host === \"web\" && problemsVisible ? SHELL.fitInsetBottomWeb : SHELL.fitInsetBottom,\n left: SHELL.fitInsetSide,\n };\n\n const bottomSheetOpen = inspector !== undefined && container.width <= SHELL.narrow;\n if (inspector !== undefined && !bottomSheetOpen) {\n insets.right = SHELL.edge + SHELL.inspectorWidth + SHELL.fitInsetSide;\n }\n if (bottomSheetOpen) {\n // Guide §5.11: the sheet hides zoom panel, pill and legend, so only the sheet itself counts.\n insets.bottom =\n inspector.sheetTop === undefined\n ? SHELL.fitInsetBottom\n : container.height - inspector.sheetTop + SHELL.edge;\n } else if (legendWidth !== undefined && legendWidth > 0 && container.width > SHELL.veryNarrow) {\n insets.left = SHELL.edge + legendWidth + SHELL.fitInsetSide;\n }\n return insets;\n}\n\n/** Relative tolerance of `sameView` (diagram-js rounds the transform to three decimals). */\nexport const VIEW_TOLERANCE = 1e-3;\n\n/**\n * True when two views are the same within `tolerance`: the scale and the translation in screen\n * pixels (`x · scale`, `y · scale`) agree relative to `max(1, |value|)`. Width and height are\n * ignored, so a container resize is never a difference.\n */\nexport function sameView(\n a: Pick<ScaledViewbox, \"x\" | \"y\" | \"scale\">,\n b: Pick<ScaledViewbox, \"x\" | \"y\" | \"scale\">,\n tolerance: number = VIEW_TOLERANCE,\n): boolean {\n const close = (first: number, second: number): boolean =>\n Math.abs(first - second) <= tolerance * Math.max(1, Math.abs(first), Math.abs(second));\n return (\n close(a.scale, b.scale) && close(a.x * a.scale, b.x * b.scale) && close(a.y * a.scale, b.y * b.scale)\n );\n}\n\n/**\n * The view at `progress` (0…1) between two viewboxes of the same container: the scale is\n * interpolated in log space, the centre linearly. Used per animation frame by the animated fit.\n */\nexport function interpolateViewbox(from: ScaledViewbox, to: ScaledViewbox, progress: number): ScaledViewbox {\n if (!(progress > 0)) return { ...from };\n if (progress >= 1) return { ...to };\n const linear = (start: number, end: number): number => start + (end - start) * progress;\n const scale =\n from.scale > 0 && to.scale > 0\n ? Math.exp(linear(Math.log(from.scale), Math.log(to.scale)))\n : linear(from.scale, to.scale);\n const centreX = linear(from.x + from.width / 2, to.x + to.width / 2);\n const centreY = linear(from.y + from.height / 2, to.y + to.height / 2);\n const width = linear(from.width * from.scale, to.width * to.scale) / scale;\n const height = linear(from.height * from.scale, to.height * to.scale) / scale;\n return { x: centreX - width / 2, y: centreY - height / 2, width, height, scale };\n}\n\n/** `--cd-ease`, `cubic-bezier(0.4, 0, 0.2, 1)`, as a function of time 0…1. */\nexport function easeStandard(progress: number): number {\n return STANDARD_EASING(progress);\n}\n\nconst STANDARD_EASING = cubicBezier(0.4, 0, 0.2, 1);\n\n/** A CSS `cubic-bezier(x1, y1, x2, y2)` timing function (`x1`, `x2` within 0…1, so x(t) is monotone). */\nfunction cubicBezier(x1: number, y1: number, x2: number, y2: number): (progress: number) => number {\n const polynomial = (first: number, second: number) => {\n const c = 3 * first;\n const b = 3 * (second - first) - c;\n const a = 1 - c - b;\n return {\n at: (t: number): number => ((a * t + b) * t + c) * t,\n slope: (t: number): number => (3 * a * t + 2 * b) * t + c,\n };\n };\n const curveX = polynomial(x1, x2);\n const curveY = polynomial(y1, y2);\n const precision = 1e-7;\n\n const parameterFor = (x: number): number => {\n let t = x;\n for (let iteration = 0; iteration < 8; iteration++) {\n const error = curveX.at(t) - x;\n if (Math.abs(error) < precision) return t;\n const slope = curveX.slope(t);\n if (Math.abs(slope) < 1e-6) break;\n t -= error / slope;\n if (t < 0 || t > 1) break;\n }\n // Newton left the curve or stalled on a flat stretch: bisection always converges.\n let low = 0;\n let high = 1;\n t = x;\n for (let iteration = 0; iteration < 60; iteration++) {\n const value = curveX.at(t);\n if (Math.abs(value - x) < precision) break;\n if (value < x) low = t;\n else high = t;\n t = (low + high) / 2;\n }\n return t;\n };\n\n return (progress) => {\n if (!(progress > 0)) return 0;\n if (progress >= 1) return 1;\n return curveY.at(parameterFor(progress));\n };\n}\n","import type { ShellMessages } from \"./messages.js\";\n\n/** diagram-js `ZoomScroll` RANGE (not configurable): 20 % … 400 %. */\nexport const ZOOM_MIN = 0.2;\nexport const ZOOM_MAX = 4;\n\n/** Zoom out is disabled at 20 % and below (`zoom <= 0.2 · 1.001`), so it never zooms in. */\nexport function canZoomOut(zoom: number): boolean {\n return zoom > ZOOM_MIN * 1.001;\n}\n\n/** Zoom in is disabled at 400 % (`zoom >= 4 · 0.999`). */\nexport function canZoomIn(zoom: number): boolean {\n return zoom < ZOOM_MAX * 0.999;\n}\n\n/**\n * A zoom as a whole percentage in the UI language: `Intl.NumberFormat(lang, { style: \"percent\",\n * maximumFractionDigits: 0 })`, so English \"86%\", German \"86 %\" (with U+00A0).\n */\nexport function formatPercent(zoom: number, lang: string): string {\n return numberFormat(lang, \"percent\").format(zoom);\n}\n\n/** Any other number in the chrome (\"2 problems\"), via `Intl.NumberFormat(lang)`. */\nexport function formatNumber(value: number, lang: string): string {\n return numberFormat(lang, \"decimal\").format(value);\n}\n\nconst FALLBACK_LANG = \"en\";\nconst formats = new Map<string, Intl.NumberFormat>();\n\n// The readout formats on every animation frame of a zoom, so each formatter is built once. A\n// language tag Intl rejects (an empty `lang=\"\"`, a typo) falls back to English, the shell's default\n// language, instead of breaking the chrome.\nfunction numberFormat(lang: string, style: \"percent\" | \"decimal\"): Intl.NumberFormat {\n const key = `${style}|${lang}`;\n let format = formats.get(key);\n if (!format) {\n const options: Intl.NumberFormatOptions =\n style === \"percent\" ? { style, maximumFractionDigits: 0 } : { style };\n try {\n format = new Intl.NumberFormat(lang, options);\n } catch {\n format = new Intl.NumberFormat(FALLBACK_LANG, options);\n }\n formats.set(key, format);\n }\n return format;\n}\n\nexport interface ZoomReadout {\n /** \"86%\" */\n text: string;\n /** \"Zoom to 100% (now 86%)\" */\n ariaLabel: string;\n canZoomOut: boolean;\n canZoomIn: boolean;\n}\n\n/** Everything the zoom panel shows for a zoom: readout text, its accessible name and the limits. */\nexport function zoomReadout(\n zoom: number,\n lang: string,\n messages: Pick<ShellMessages, \"zoomToActualNow\">,\n): ZoomReadout {\n const text = formatPercent(zoom, lang);\n return {\n text,\n ariaLabel: messages.zoomToActualNow(formatPercent(1, lang), text),\n canZoomOut: canZoomOut(zoom),\n canZoomIn: canZoomIn(zoom),\n };\n}\n","import type { ShellMessages } from \"./messages.js\";\nimport type { ShellHost } from \"./types.js\";\n\n/** The only keys the chrome handles (guide §5.8). */\nexport type ChromeKey = \"zoomIn\" | \"zoomOut\" | \"zoomToActual\" | \"fit\";\n\n/** The `KeyboardEvent` fields the keymap reads. */\nexport interface KeyLike {\n key: string;\n code: string;\n metaKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n isComposing?: boolean | undefined;\n}\n\n/**\n * Which chrome key `event` is, ignoring where it was pressed:\n * - `zoomIn`: Cmd/Ctrl + `+` or `=` (by `key`, with or without Shift), or `NumpadAdd` (by `code`);\n * - `zoomOut`: Cmd/Ctrl + `-` or `_`, or `NumpadSubtract`;\n * - `zoomToActual`: Cmd/Ctrl + `0`, or `Numpad0` (also with NumLock off);\n * - `fit`: Shift + `Digit1` (by `code`, every layout) without Cmd, Ctrl or Alt.\n * Cmd/Ctrl is `metaKey || ctrlKey` on every platform; nothing matches with `altKey` (AltGr).\n */\nexport function matchChromeKey(event: KeyLike): ChromeKey | undefined {\n if (event.altKey) return undefined;\n if (isCommand(event)) {\n if (event.key === \"+\" || event.key === \"=\" || event.code === \"NumpadAdd\") return \"zoomIn\";\n if (event.key === \"-\" || event.key === \"_\" || event.code === \"NumpadSubtract\") return \"zoomOut\";\n if (event.key === \"0\" || event.code === \"Numpad0\") return \"zoomToActual\";\n return undefined;\n }\n return event.shiftKey && event.code === \"Digit1\" ? \"fit\" : undefined;\n}\n\nfunction isCommand(event: KeyLike): boolean {\n return event.metaKey || event.ctrlKey;\n}\n\n/**\n * Cmd/Ctrl + Z, Cmd/Ctrl + Shift + Z or Cmd/Ctrl + Y, without Alt (by `key`, `code` as fallback).\n * Cmd/Ctrl + Shift + Y is not redo (VS Code toggles its Debug Console with it).\n */\nexport function isUndoRedoKey(event: KeyLike): boolean {\n if (event.altKey || !isCommand(event)) return false;\n const letter = undoRedoLetter(event);\n return letter === \"z\" || (letter === \"y\" && !event.shiftKey);\n}\n\n// The produced character decides on Latin layouts (QWERTZ swaps the Y and Z keys); only where the\n// layout produces no ASCII character (Cyrillic, Greek, …) the physical key stands in.\nfunction undoRedoLetter(event: KeyLike): string | undefined {\n if (event.key.length === 1 && event.key.charCodeAt(0) < 0x80) return event.key.toLowerCase();\n if (event.code === \"KeyZ\") return \"z\";\n if (event.code === \"KeyY\") return \"y\";\n return undefined;\n}\n\n/** A text field: every input, select list and editable text (label editor, inspector, dialogs). */\nexport const TEXT_FIELD_SELECTOR =\n \"input, textarea, select, [contenteditable]:not([contenteditable='false'])\";\n\n/** Where a key was pressed, as the shell's one `keydown` listener sees it. */\nexport interface KeyContext {\n host: ShellHost;\n /** The event target matches `TEXT_FIELD_SELECTOR` or is `isContentEditable`. */\n inTextField: boolean;\n /** The shell's menu dropdown is open. */\n menuOpen: boolean;\n /** A popover or dialog (`[role=\"dialog\"]`, modal or not) is open. */\n dialogOpen: boolean;\n /** A modal dialog (`[role=\"dialog\"][aria-modal=\"true\"]`) is open: chrome keys do not act. */\n modalOpen: boolean;\n /** `event.defaultPrevented` when the listener ran: the canvas (or the modeler) handled the key. */\n defaultPrevented: boolean;\n}\n\nexport type KeyAction = ChromeKey | \"closeMenu\";\n\nexport interface KeyDecision {\n /** What the shell executes; `undefined` = nothing. */\n action?: KeyAction;\n preventDefault: boolean;\n stopPropagation: boolean;\n}\n\n/**\n * The key rules of guide §5.8 and §13.5 in one place:\n * 1. Escape with the menu open: `closeMenu`, prevented; in the IDE host stopped whenever the menu\n * or a dialog/popover is open. During IME composition Escape belongs to the IME: no `closeMenu`.\n * 2. Zoom keys: executed and prevented unless composing, in a text field or with a modal dialog;\n * in the IDE host always stopped, also when not executed (VS Code must never zoom the window).\n * 3. Shift+1: `fit` under the same conditions; otherwise untouched (it types \"!\").\n * 4. IDE host, text field, undo/redo key: stopped only (the field keeps its native undo).\n * 5. IDE host, `defaultPrevented`: stopped (the canvas handled it; VS Code must not run it again).\n * Everything else passes untouched. `stopPropagation` is only ever true in the IDE host.\n */\nexport function decideKey(event: KeyLike, context: KeyContext): KeyDecision {\n const stop = context.host === \"ide\";\n const composing = event.isComposing === true;\n\n if (event.key === \"Escape\" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey) {\n if (context.menuOpen && !composing) {\n return { action: \"closeMenu\", preventDefault: true, stopPropagation: stop };\n }\n if (context.menuOpen || context.dialogOpen) return { preventDefault: false, stopPropagation: stop };\n }\n\n const chromeKey = matchChromeKey(event);\n if (chromeKey !== undefined) {\n const blocked = composing || context.inTextField || context.modalOpen;\n if (!blocked) return { action: chromeKey, preventDefault: true, stopPropagation: stop };\n // Blocked zoom keys are still kept from VS Code (it would zoom the window); a blocked Shift+1\n // is a character and travels on like any other.\n return { preventDefault: false, stopPropagation: chromeKey === \"fit\" ? false : stop };\n }\n\n if (context.inTextField && isUndoRedoKey(event)) return { preventDefault: false, stopPropagation: stop };\n if (context.defaultPrevented) return { preventDefault: false, stopPropagation: stop };\n return { preventDefault: false, stopPropagation: false };\n}\n\n/** The keyboard the user sits at; decides the shortcut labels (guide §5.8). */\nexport type Platform = \"mac\" | \"other\";\n\n/** The `navigator` fields `detectPlatform` reads. */\nexport interface NavigatorLike {\n platform?: string | undefined;\n userAgentData?: { platform?: string | undefined } | undefined;\n}\n\n/**\n * `mac` when `userAgentData.platform ?? platform` matches `/mac/i`, else `other`; `other` without a\n * navigator. Read it in the webview or page itself, never from the extension host (VS Code Remote).\n */\nexport function detectPlatform(navigator?: NavigatorLike | undefined): Platform {\n // An empty userAgentData.platform means \"unknown\", not \"not a Mac\": fall back to navigator.platform.\n const platform = navigator?.userAgentData?.platform || navigator?.platform || \"\";\n return /mac/i.test(platform) ? \"mac\" : \"other\";\n}\n\n/**\n * The tooltip chip text: macOS `⌘+`, `⌘−` (U+2212), `⌘0`, `⇧1`; elsewhere `Ctrl+Plus`,\n * `Ctrl+Minus`, `Ctrl+0`, `Shift+1` with the key names of the messages table.\n */\nexport function shortcutLabel(\n key: ChromeKey,\n platform: Platform,\n keyNames: ShellMessages[\"keyNames\"],\n): string {\n if (platform === \"mac\") return MAC_SHORTCUT_LABELS[key];\n switch (key) {\n case \"zoomIn\":\n return `${keyNames.ctrl}+${keyNames.plus}`;\n case \"zoomOut\":\n return `${keyNames.ctrl}+${keyNames.minus}`;\n case \"zoomToActual\":\n return `${keyNames.ctrl}+0`;\n case \"fit\":\n return `${keyNames.shift}+1`;\n }\n}\n\nconst MAC_SHORTCUT_LABELS: Readonly<Record<ChromeKey, string>> = {\n zoomIn: \"⌘+\",\n zoomOut: \"⌘\\u2212\",\n zoomToActual: \"⌘0\",\n fit: \"⇧1\",\n};\n\n/**\n * The `aria-keyshortcuts` value (WAI-ARIA names): zoom in `Meta+= Meta+Plus`, zoom out `Meta+-`,\n * 100 % `Meta+0`, fit `Shift+1`; outside macOS `Control` instead of `Meta`.\n */\nexport function ariaKeyShortcuts(key: ChromeKey, platform: Platform): string {\n const command = platform === \"mac\" ? \"Meta\" : \"Control\";\n switch (key) {\n case \"zoomIn\":\n return `${command}+= ${command}+Plus`;\n case \"zoomOut\":\n return `${command}+-`;\n case \"zoomToActual\":\n return `${command}+0`;\n case \"fit\":\n return \"Shift+1\";\n }\n}\n\nconst MAC_MODIFIER_SYMBOLS: Readonly<Record<string, string>> = {\n \"⌘\": \"Meta\",\n \"⇧\": \"Shift\",\n \"⌥\": \"Alt\",\n \"⌃\": \"Control\",\n};\n\nconst MODIFIER_NAMES: Readonly<Record<string, string>> = {\n cmd: \"Meta\",\n command: \"Meta\",\n meta: \"Meta\",\n ctrl: \"Control\",\n control: \"Control\",\n strg: \"Control\",\n shift: \"Shift\",\n umschalt: \"Shift\",\n alt: \"Alt\",\n option: \"Alt\",\n opt: \"Alt\",\n};\n\nconst NAMED_KEYS: Readonly<Record<string, string>> = {\n esc: \"Escape\",\n escape: \"Escape\",\n del: \"Delete\",\n delete: \"Delete\",\n entf: \"Delete\",\n backspace: \"Backspace\",\n enter: \"Enter\",\n return: \"Enter\",\n tab: \"Tab\",\n space: \"Space\",\n home: \"Home\",\n end: \"End\",\n pageup: \"PageUp\",\n pagedown: \"PageDown\",\n arrowup: \"ArrowUp\",\n arrowdown: \"ArrowDown\",\n arrowleft: \"ArrowLeft\",\n arrowright: \"ArrowRight\",\n \"↑\": \"ArrowUp\",\n \"↓\": \"ArrowDown\",\n \"←\": \"ArrowLeft\",\n \"→\": \"ArrowRight\",\n plus: \"Plus\",\n};\n\nfunction ariaKeyName(key: string): string | undefined {\n const named = NAMED_KEYS[key.toLowerCase()];\n if (named) return named;\n if (/^f\\d{1,2}$/i.test(key)) return key.toUpperCase();\n if (key.length !== 1 || MAC_MODIFIER_SYMBOLS[key]) return undefined;\n if (key === \"+\") return \"Plus\";\n if (key === \" \") return \"Space\";\n return key.toUpperCase();\n}\n\n/**\n * The `aria-keyshortcuts` value (WAI-ARIA names, guide §5.6) for a shortcut chip label: `\"L\"` →\n * `\"L\"`, `\"⇧L\"` → `\"Shift+L\"`, `\"⌘⇧Z\"` → `\"Meta+Shift+Z\"`, `\"Ctrl+Shift+E\"` → `\"Control+Shift+E\"`,\n * `\"Del\"` → `\"Delete\"`, `\"Ctrl++\"` → `\"Control+Plus\"`. `undefined` for a label that is not one key\n * combination (`\"G then D\"`, `\"1–9\"`, empty).\n */\nexport function ariaKeyShortcutsFromLabel(label: string): string | undefined {\n let rest = label.trim();\n const modifiers: string[] = [];\n for (let symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)]; symbol && rest.length > 1;) {\n modifiers.push(symbol);\n rest = rest.slice(1).trim();\n symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)];\n }\n if (!rest || /\\s/.test(rest.replace(/\\s*\\+\\s*/g, \"+\"))) return undefined;\n const parts = rest.split(\"+\").map((part) => part.trim());\n let key = parts.pop() ?? \"\";\n if (key === \"\" && parts.at(-1) === \"\") {\n parts.pop();\n key = \"+\";\n }\n for (const part of parts) {\n const modifier = MODIFIER_NAMES[part.toLowerCase()];\n if (!modifier) return undefined;\n modifiers.push(modifier);\n }\n const name = ariaKeyName(key);\n return name ? [...new Set(modifiers), name].join(\"+\") : undefined;\n}\n","/** The modes a Miragon modeler can show (guide §13.3). */\nexport type Theme = \"light\" | \"dark\";\n\n/** High contrast; written as `data-cd-kontrast=\"hoch\"` on the same element as `data-cd-theme`. */\nexport type Kontrast = \"hoch\";\n\nexport interface Appearance {\n theme: Theme;\n kontrast?: Kontrast | undefined;\n}\n\n/**\n * The one appearance decision of guide §13.5: a theme the modeler cannot show is never shown\n * (`themes[0]` wins, so `[\"light\"]` stays light whatever the host requests); `kontrast` always\n * passes through. Throws when `themes` is empty.\n */\nexport function resolveAppearance(requested: Appearance, themes: readonly Theme[]): Appearance {\n const [fallback] = themes;\n if (fallback === undefined) {\n throw new Error(\n \"@miragon/modeler-shell: resolveAppearance needs at least one theme the modeler can show\",\n );\n }\n const theme = themes.includes(requested.theme) ? requested.theme : fallback;\n return withKontrast(theme, requested.kontrast);\n}\n\n// Appearances arrive through postMessage and settings, so only the one known value passes.\nfunction withKontrast(theme: Theme, kontrast: unknown): Appearance {\n return kontrast === \"hoch\" ? { theme, kontrast } : { theme };\n}\n\n/**\n * The appearance VS Code requests for `vscode.window.activeColorTheme.kind`:\n * 1 Light → light, 2 Dark → dark, 3 HighContrast → dark + hoch, 4 HighContrastLight → light + hoch;\n * any other value → light.\n */\nexport function appearanceFromColorThemeKind(kind: number): Appearance {\n switch (kind) {\n case 2:\n return { theme: \"dark\" };\n case 3:\n return { theme: \"dark\", kontrast: \"hoch\" };\n case 4:\n return { theme: \"light\", kontrast: \"hoch\" };\n default:\n return { theme: \"light\" };\n }\n}\n\n/** The values of the setting `miragon.<modeler>.colorTheme` (it ships once a modeler can show dark). */\nexport type ColorThemeSetting = \"automatic\" | \"light\" | \"dark\";\n\n/**\n * The requested appearance in an IDE host: the setting's theme unless it is `automatic` (or\n * missing), the contrast always from the color theme kind.\n */\nexport function requestedAppearance(kind: number, setting?: ColorThemeSetting | undefined): Appearance {\n const fromKind = appearanceFromColorThemeKind(kind);\n const theme = setting === \"light\" || setting === \"dark\" ? setting : fromKind.theme;\n return withKontrast(theme, fromKind.kontrast);\n}\n\n/**\n * The fallback signal inside a VS Code webview: the body classes `vscode-high-contrast-light`\n * (checked first, VS Code adds `vscode-high-contrast` to it as well), `vscode-high-contrast`,\n * `vscode-dark`, `vscode-light`. `undefined` when none is set. Never use them for styling.\n */\nexport function appearanceFromBodyClasses(classes: Iterable<string>): Appearance | undefined {\n const present = new Set(classes);\n if (present.has(\"vscode-high-contrast-light\")) return { theme: \"light\", kontrast: \"hoch\" };\n if (present.has(\"vscode-high-contrast\")) return { theme: \"dark\", kontrast: \"hoch\" };\n if (present.has(\"vscode-dark\")) return { theme: \"dark\" };\n if (present.has(\"vscode-light\")) return { theme: \"light\" };\n return undefined;\n}\n\n/**\n * VS Code's `workbench.reduceMotion` as the webview sees it: the body class `vscode-reduce-motion`\n * (the webview host page sets it). Never use it for styling; the shell mirrors it as\n * `REDUCED_MOTION_CLASS` on `<html>`.\n */\nexport function reducedMotionFromBodyClasses(classes: Iterable<string>): boolean {\n return new Set(classes).has(\"vscode-reduce-motion\");\n}\n\n/**\n * On `<html>` while the host asks for reduced motion although the OS does not (VS Code's\n * `workbench.reduceMotion`): the shell's animations stop as under `prefers-reduced-motion`, and an\n * explicit fit jumps.\n */\nexport const REDUCED_MOTION_CLASS = \"cd-shell-reduce-motion\";\n\nexport interface AppearanceAttributes {\n \"data-cd-theme\": Theme;\n \"data-cd-kontrast\"?: Kontrast;\n}\n\n/** The attributes for `<html>` (own document) or the modeler container (foreign page). */\nexport function appearanceAttributes(appearance: Appearance): AppearanceAttributes {\n return appearance.kontrast === \"hoch\"\n ? { \"data-cd-theme\": appearance.theme, \"data-cd-kontrast\": \"hoch\" }\n : { \"data-cd-theme\": appearance.theme };\n}\n","/**\n * Every text the shell shows or announces, in one table (guide §2, shared terms). English by\n * default; a localized modeler overrides the whole table in its language, numbers arrive already\n * formatted through `Intl` in that language.\n */\nexport interface ShellMessages {\n /** Menu trigger `aria-label` and tooltip, dropdown `aria-label`. */\n menu: string;\n /** Palette `aria-label`. */\n tools: string;\n /** `aria-label` of the focusable canvas `<svg>` (`role=\"application\"`); a modeler names its notation. */\n canvas: string;\n /** Zoom panel `aria-label`. */\n zoom: string;\n zoomOut: string;\n zoomIn: string;\n /** Tooltip of the readout, e.g. `(percent) => \"Zoom to \" + percent` with percent \"100%\". */\n zoomToActual(percent: string): string;\n /** `aria-label` of the readout: \"Zoom to 100% (now 86%)\". */\n zoomToActualNow(percent: string, now: string): string;\n fit: string;\n /** Legend title and the Model-group checkbox. */\n legend: string;\n closeLegend: string;\n /** Pill text: \"1 problem\", \"2 problems\". */\n problems(count: number, formattedCount: string): string;\n /** Pill `aria-label`: \"2 problems, 1 error\", \"2 problems, no errors\". */\n problemsLabel(count: number, errors: number, formattedCount: string, formattedErrors: string): string;\n /** Pill tooltip in the IDE host. */\n showProblems: string;\n /** Empty-file hint (guide §13.5). */\n emptyHint: string;\n insertExample: string;\n /** Status box for an invalid file; `diagram` names the notation (\"Event Storming board\"). */\n invalidFile(diagram: string): string;\n /** Key names in shortcut chips outside macOS (German: Strg, Umschalt, Plus, Minus). */\n keyNames: { ctrl: string; shift: string; plus: string; minus: string };\n}\n\n/** Every key optional; an `undefined` value keeps the default (a conditional override). */\nexport type ShellMessageOverrides = {\n [Key in Exclude<keyof ShellMessages, \"keyNames\">]?: ShellMessages[Key] | undefined;\n} & {\n keyNames?: { [Key in keyof ShellMessages[\"keyNames\"]]?: string | undefined } | undefined;\n};\n\nexport const defaultMessages: ShellMessages = {\n menu: \"Menu\",\n tools: \"Tools\",\n canvas: \"Diagram\",\n zoom: \"Zoom\",\n zoomOut: \"Zoom out\",\n zoomIn: \"Zoom in\",\n zoomToActual: (percent) => `Zoom to ${percent}`,\n zoomToActualNow: (percent, now) => `Zoom to ${percent} (now ${now})`,\n fit: \"Fit to view\",\n legend: \"Legend\",\n closeLegend: \"Close legend\",\n problems: (count, formattedCount) => `${formattedCount} ${count === 1 ? \"problem\" : \"problems\"}`,\n problemsLabel: (count, errors, formattedCount, formattedErrors) =>\n `${formattedCount} ${count === 1 ? \"problem\" : \"problems\"}, ${\n errors === 0 ? \"no errors\" : `${formattedErrors} ${errors === 1 ? \"error\" : \"errors\"}`\n }`,\n showProblems: \"Show in Problems view\",\n emptyHint: \"Pick a tool from the palette to start.\",\n insertExample: \"Insert example\",\n invalidFile: (diagram) =>\n `This file isn't a valid ${diagram} right now. The diagram shows the last valid version and is read-only until you fix the text.`,\n keyNames: { ctrl: \"Ctrl\", shift: \"Shift\", plus: \"Plus\", minus: \"Minus\" },\n};\n\n/** `defaultMessages` with `overrides` applied (`keyNames` merged key by key). */\nexport function resolveMessages(overrides?: ShellMessageOverrides | undefined): ShellMessages {\n const { keyNames, ...texts } = overrides ?? {};\n return {\n ...defaultMessages,\n ...definedEntries(texts),\n keyNames: { ...defaultMessages.keyNames, ...definedEntries(keyNames ?? {}) },\n };\n}\n\n// `{ menu: undefined }` (a spread of optional fields, a conditional override) keeps the default\n// instead of blanking the text.\nfunction definedEntries<T extends object>(partial: T): { [Key in keyof T]?: Exclude<T[Key], undefined> } {\n return Object.fromEntries(Object.entries(partial).filter(([, value]) => value !== undefined)) as {\n [Key in keyof T]?: Exclude<T[Key], undefined>;\n };\n}\n","import { SHELL } from \"./sizes.js\";\nimport type { Bounds, PaletteRow, Point, Size } from \"./types.js\";\n\n/** Free space the top row needs between the palette and a neighbouring island (guide §5.15). */\nexport const PALETTE_CLEARANCE = 16;\n\n/** Hover delay of the chrome tooltip, equal to diagram-js `HoverTooltip` `SHOW_DELAY`. */\nexport const TOOLTIP_DELAY_MS = 300;\n\n/** The status box for an invalid file shows after this long without a valid import (guide §5.12). */\nexport const INVALID_STATUS_DELAY_MS = 800;\n\nexport interface PaletteRowInput {\n containerWidth: number;\n /**\n * The palette's intrinsic border-box width (`scrollWidth` plus its borders), so the decision does\n * not flip once the second row clips it.\n */\n paletteWidth: number;\n /** Right edge of the menu trigger, relative to the shell root. */\n menuRight: number;\n /** Left edge of the primary action (web app), relative to the shell root; absent in IDE hosts. */\n primaryLeft?: number | undefined;\n}\n\n/**\n * `second-row` when the container is at most `SHELL.narrow` wide or a centred palette would keep\n * less than `PALETTE_CLEARANCE` to the menu trigger (or the primary action); `top` otherwise.\n */\nexport function decidePaletteRow(input: PaletteRowInput): PaletteRow {\n const { containerWidth, paletteWidth, menuRight, primaryLeft } = input;\n if (containerWidth <= SHELL.narrow) return \"second-row\";\n const paletteLeft = (containerWidth - paletteWidth) / 2;\n const paletteRight = (containerWidth + paletteWidth) / 2;\n if (paletteLeft < menuRight + PALETTE_CLEARANCE) return \"second-row\";\n if (primaryLeft !== undefined && paletteRight > primaryLeft - PALETTE_CLEARANCE) return \"second-row\";\n return \"top\";\n}\n\n/** The legend starts open unless the container is at most `SHELL.low` high (guide §5.10). */\nexport function legendStartsOpen(containerHeight: number): boolean {\n return containerHeight > SHELL.low;\n}\n\nexport interface TooltipPlacementInput {\n /** The hovered or focused control, relative to the shell root (horizontal centre). */\n anchor: Bounds;\n /** The island the control sits in, relative to the shell root (vertical placement). */\n island: Bounds;\n tooltip: Size;\n container: Size;\n}\n\n/**\n * The tooltip's top-left corner relative to the shell root (guide §5.6): below an island in the\n * upper half (`island.bottom + 8`: top row 60, second row 112), above one in the lower half\n * (`island.top − 8 − height`: bottom row ends at `bottom: 60px`); centred on the anchor, shifted to\n * keep `SHELL.edge` to the container's sides.\n */\nexport function placeTooltip(input: TooltipPlacementInput): Point {\n const { anchor, island, tooltip, container } = input;\n const inUpperHalf = island.y + island.height / 2 < container.height / 2;\n const y = inUpperHalf ? island.y + island.height + SHELL.gap : island.y - SHELL.gap - tooltip.height;\n const centred = anchor.x + anchor.width / 2 - tooltip.width / 2;\n const rightmost = container.width - SHELL.edge - tooltip.width;\n // In a container narrower than the tooltip plus both edges, the left edge wins.\n const x = Math.max(SHELL.edge, Math.min(centred, rightmost));\n return { x, y };\n}\n"],"mappings":"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","names":[]}
|
package/dist/core.d.cts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { d as Size, c as ShellHost, P as PaletteRow, I as Insets, B as Bounds, S as ScaledViewbox, b as Point } from './types-B6NfkiUG.cjs';
|
|
2
|
+
export { C as CHROME_ISLANDS, a as ChromeIsland, V as ViewportState, Z as ZoomTarget } from './types-B6NfkiUG.cjs';
|
|
3
|
+
import { d as ShellMessages } from './keymap-CObQE6q6.cjs';
|
|
4
|
+
export { C as ChromeKey, K as KeyAction, a as KeyContext, b as KeyDecision, c as KeyLike, N as NavigatorLike, P as Platform, S as ShellMessageOverrides, T as TEXT_FIELD_SELECTOR, e as ariaKeyShortcuts, f as ariaKeyShortcutsFromLabel, g as decideKey, h as defaultMessages, i as detectPlatform, j as isUndoRedoKey, m as matchChromeKey, r as resolveMessages, s as shortcutLabel } from './keymap-CObQE6q6.cjs';
|
|
5
|
+
export { A as Appearance, a as AppearanceAttributes, C as ColorThemeSetting, K as Kontrast, R as REDUCED_MOTION_CLASS, T as Theme, b as appearanceAttributes, c as appearanceFromBodyClasses, d as appearanceFromColorThemeKind, r as reducedMotionFromBodyClasses, e as requestedAppearance, f as resolveAppearance } from './appearance-D3FUcbu6.cjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The shell dimensions of the design system (guide §5.1, `shell` group of
|
|
9
|
+
* `cd-tokens.generated.json`), keyed by a readable name. Scripts use `SHELL`; CSS uses the
|
|
10
|
+
* `var(--cd-shell-*)` variables the consumer vendors. A drift test pins `SHELL` to the token JSON.
|
|
11
|
+
*/
|
|
12
|
+
declare const SHELL_TOKENS: {
|
|
13
|
+
readonly edge: "--cd-shell-rand";
|
|
14
|
+
readonly gap: "--cd-shell-luecke";
|
|
15
|
+
readonly row: "--cd-shell-zeile";
|
|
16
|
+
readonly target: "--cd-shell-ziel";
|
|
17
|
+
readonly entry: "--cd-shell-eintrag";
|
|
18
|
+
readonly free: "--cd-shell-frei";
|
|
19
|
+
readonly menuMinWidth: "--cd-shell-menue-breite";
|
|
20
|
+
readonly inspectorWidth: "--cd-shell-inspector-breite";
|
|
21
|
+
readonly fitInsetTop: "--cd-shell-einpassen-oben";
|
|
22
|
+
readonly fitInsetTopSecondRow: "--cd-shell-einpassen-oben-schmal";
|
|
23
|
+
readonly fitInsetSide: "--cd-shell-einpassen-seite";
|
|
24
|
+
readonly fitInsetBottom: "--cd-shell-einpassen-unten";
|
|
25
|
+
readonly fitInsetBottomWeb: "--cd-shell-einpassen-unten-web";
|
|
26
|
+
readonly narrow: "--cd-shell-schmal";
|
|
27
|
+
readonly veryNarrow: "--cd-shell-sehr-schmal";
|
|
28
|
+
readonly low: "--cd-shell-niedrig";
|
|
29
|
+
readonly layerIslands: "--cd-shell-ebene-inseln";
|
|
30
|
+
readonly layerInspector: "--cd-shell-ebene-inspector";
|
|
31
|
+
readonly layerTopRow: "--cd-shell-ebene-reihe";
|
|
32
|
+
readonly layerPopups: "--cd-shell-ebene-aufklapper";
|
|
33
|
+
readonly layerDialog: "--cd-shell-ebene-dialog";
|
|
34
|
+
readonly layerToast: "--cd-shell-ebene-toast";
|
|
35
|
+
};
|
|
36
|
+
type ShellSizeName = keyof typeof SHELL_TOKENS;
|
|
37
|
+
type ShellSizes = Readonly<Record<ShellSizeName, number>>;
|
|
38
|
+
/** Pixels (layers: plain z-index numbers), equal to `parseFloat` of the token JSON values. */
|
|
39
|
+
declare const SHELL: ShellSizes;
|
|
40
|
+
/**
|
|
41
|
+
* Reads the `shell` group of `cd-tokens.generated.json` (`{ "--cd-shell-rand": "12px", … }`) into
|
|
42
|
+
* numbers. Throws when a size of `SHELL_TOKENS` is missing or not a number.
|
|
43
|
+
*/
|
|
44
|
+
declare function shellSizesFromTokens(shellGroup: Readonly<Record<string, string>>): ShellSizes;
|
|
45
|
+
|
|
46
|
+
/** Fit never zooms above 100 % (guide §5.9). */
|
|
47
|
+
declare const MAX_FIT_ZOOM = 1;
|
|
48
|
+
/** Duration of the explicit, animated fit (`--cd-motion-base`). */
|
|
49
|
+
declare const FIT_ANIMATION_MS = 250;
|
|
50
|
+
/**
|
|
51
|
+
* The viewbox that shows `bounds` centred in the free rectangle (`container` minus `insets`) at the
|
|
52
|
+
* largest scale that fits, capped at `maxZoom`. The box has the container's aspect ratio, so
|
|
53
|
+
* diagram-js's `canvas.viewbox(box)` sets exactly this view. Returns `undefined` when the container
|
|
54
|
+
* is 0 × 0 or `bounds` has neither width nor height; a zero width or height scales by the other
|
|
55
|
+
* dimension. Where the insets leave no free room on an axis (a container lower or narrower than
|
|
56
|
+
* its chrome), that axis centres in the whole container instead.
|
|
57
|
+
*/
|
|
58
|
+
declare function fitViewbox(bounds: Bounds, container: Size, insets: Insets, maxZoom?: number): ScaledViewbox | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* The centre of the free rectangle (`container` minus `insets`) in pixels from the container's top
|
|
61
|
+
* left, where `fitViewbox` centres the content and keyboard creation places an element (guide
|
|
62
|
+
* §5.5). An axis the insets leave no room on centres in the whole container, as in `fitViewbox`.
|
|
63
|
+
* Returns `undefined` when the container is 0 × 0.
|
|
64
|
+
*/
|
|
65
|
+
declare function freeRectCenter(container: Size, insets: Insets): Point | undefined;
|
|
66
|
+
/** What is open or shown when a fit runs (guide §5.9: every fit counts what is open right now). */
|
|
67
|
+
interface FitLayout {
|
|
68
|
+
container: Size;
|
|
69
|
+
host: ShellHost;
|
|
70
|
+
paletteRow: PaletteRow;
|
|
71
|
+
/** The problems pill is shown (only the web app reserves room for it). */
|
|
72
|
+
problemsVisible: boolean;
|
|
73
|
+
/** Rendered width of the open legend; `undefined` when it is closed or the modeler has none. */
|
|
74
|
+
legendWidth?: number | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* The open inspector; `undefined` when closed. Above `SHELL.narrow` it is the side panel; up to it
|
|
77
|
+
* a bottom sheet whose top edge (`sheetTop`, px from the container top) must be measured.
|
|
78
|
+
*/
|
|
79
|
+
inspector?: {
|
|
80
|
+
sheetTop?: number | undefined;
|
|
81
|
+
} | undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The fit insets of guide §5.9: top 88 (second row 140), sides 24, bottom 64 (web app with pill 92),
|
|
85
|
+
* right 324 with an open side inspector, left 12 + legend width + 24 with an open legend above
|
|
86
|
+
* `SHELL.veryNarrow`, bottom = distance to the sheet's top edge + 12 with an open bottom sheet.
|
|
87
|
+
*/
|
|
88
|
+
declare function fitInsets(layout: FitLayout): Insets;
|
|
89
|
+
/** Relative tolerance of `sameView` (diagram-js rounds the transform to three decimals). */
|
|
90
|
+
declare const VIEW_TOLERANCE = 0.001;
|
|
91
|
+
/**
|
|
92
|
+
* True when two views are the same within `tolerance`: the scale and the translation in screen
|
|
93
|
+
* pixels (`x · scale`, `y · scale`) agree relative to `max(1, |value|)`. Width and height are
|
|
94
|
+
* ignored, so a container resize is never a difference.
|
|
95
|
+
*/
|
|
96
|
+
declare function sameView(a: Pick<ScaledViewbox, "x" | "y" | "scale">, b: Pick<ScaledViewbox, "x" | "y" | "scale">, tolerance?: number): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The view at `progress` (0…1) between two viewboxes of the same container: the scale is
|
|
99
|
+
* interpolated in log space, the centre linearly. Used per animation frame by the animated fit.
|
|
100
|
+
*/
|
|
101
|
+
declare function interpolateViewbox(from: ScaledViewbox, to: ScaledViewbox, progress: number): ScaledViewbox;
|
|
102
|
+
/** `--cd-ease`, `cubic-bezier(0.4, 0, 0.2, 1)`, as a function of time 0…1. */
|
|
103
|
+
declare function easeStandard(progress: number): number;
|
|
104
|
+
|
|
105
|
+
/** diagram-js `ZoomScroll` RANGE (not configurable): 20 % … 400 %. */
|
|
106
|
+
declare const ZOOM_MIN = 0.2;
|
|
107
|
+
declare const ZOOM_MAX = 4;
|
|
108
|
+
/** Zoom out is disabled at 20 % and below (`zoom <= 0.2 · 1.001`), so it never zooms in. */
|
|
109
|
+
declare function canZoomOut(zoom: number): boolean;
|
|
110
|
+
/** Zoom in is disabled at 400 % (`zoom >= 4 · 0.999`). */
|
|
111
|
+
declare function canZoomIn(zoom: number): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* A zoom as a whole percentage in the UI language: `Intl.NumberFormat(lang, { style: "percent",
|
|
114
|
+
* maximumFractionDigits: 0 })`, so English "86%", German "86 %" (with U+00A0).
|
|
115
|
+
*/
|
|
116
|
+
declare function formatPercent(zoom: number, lang: string): string;
|
|
117
|
+
/** Any other number in the chrome ("2 problems"), via `Intl.NumberFormat(lang)`. */
|
|
118
|
+
declare function formatNumber(value: number, lang: string): string;
|
|
119
|
+
interface ZoomReadout {
|
|
120
|
+
/** "86%" */
|
|
121
|
+
text: string;
|
|
122
|
+
/** "Zoom to 100% (now 86%)" */
|
|
123
|
+
ariaLabel: string;
|
|
124
|
+
canZoomOut: boolean;
|
|
125
|
+
canZoomIn: boolean;
|
|
126
|
+
}
|
|
127
|
+
/** Everything the zoom panel shows for a zoom: readout text, its accessible name and the limits. */
|
|
128
|
+
declare function zoomReadout(zoom: number, lang: string, messages: Pick<ShellMessages, "zoomToActualNow">): ZoomReadout;
|
|
129
|
+
|
|
130
|
+
/** Free space the top row needs between the palette and a neighbouring island (guide §5.15). */
|
|
131
|
+
declare const PALETTE_CLEARANCE = 16;
|
|
132
|
+
/** Hover delay of the chrome tooltip, equal to diagram-js `HoverTooltip` `SHOW_DELAY`. */
|
|
133
|
+
declare const TOOLTIP_DELAY_MS = 300;
|
|
134
|
+
/** The status box for an invalid file shows after this long without a valid import (guide §5.12). */
|
|
135
|
+
declare const INVALID_STATUS_DELAY_MS = 800;
|
|
136
|
+
interface PaletteRowInput {
|
|
137
|
+
containerWidth: number;
|
|
138
|
+
/**
|
|
139
|
+
* The palette's intrinsic border-box width (`scrollWidth` plus its borders), so the decision does
|
|
140
|
+
* not flip once the second row clips it.
|
|
141
|
+
*/
|
|
142
|
+
paletteWidth: number;
|
|
143
|
+
/** Right edge of the menu trigger, relative to the shell root. */
|
|
144
|
+
menuRight: number;
|
|
145
|
+
/** Left edge of the primary action (web app), relative to the shell root; absent in IDE hosts. */
|
|
146
|
+
primaryLeft?: number | undefined;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* `second-row` when the container is at most `SHELL.narrow` wide or a centred palette would keep
|
|
150
|
+
* less than `PALETTE_CLEARANCE` to the menu trigger (or the primary action); `top` otherwise.
|
|
151
|
+
*/
|
|
152
|
+
declare function decidePaletteRow(input: PaletteRowInput): PaletteRow;
|
|
153
|
+
/** The legend starts open unless the container is at most `SHELL.low` high (guide §5.10). */
|
|
154
|
+
declare function legendStartsOpen(containerHeight: number): boolean;
|
|
155
|
+
interface TooltipPlacementInput {
|
|
156
|
+
/** The hovered or focused control, relative to the shell root (horizontal centre). */
|
|
157
|
+
anchor: Bounds;
|
|
158
|
+
/** The island the control sits in, relative to the shell root (vertical placement). */
|
|
159
|
+
island: Bounds;
|
|
160
|
+
tooltip: Size;
|
|
161
|
+
container: Size;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The tooltip's top-left corner relative to the shell root (guide §5.6): below an island in the
|
|
165
|
+
* upper half (`island.bottom + 8`: top row 60, second row 112), above one in the lower half
|
|
166
|
+
* (`island.top − 8 − height`: bottom row ends at `bottom: 60px`); centred on the anchor, shifted to
|
|
167
|
+
* keep `SHELL.edge` to the container's sides.
|
|
168
|
+
*/
|
|
169
|
+
declare function placeTooltip(input: TooltipPlacementInput): Point;
|
|
170
|
+
|
|
171
|
+
export { Bounds, FIT_ANIMATION_MS, type FitLayout, INVALID_STATUS_DELAY_MS, Insets, MAX_FIT_ZOOM, PALETTE_CLEARANCE, PaletteRow, type PaletteRowInput, Point, SHELL, SHELL_TOKENS, ScaledViewbox, ShellHost, ShellMessages, type ShellSizeName, type ShellSizes, Size, TOOLTIP_DELAY_MS, type TooltipPlacementInput, VIEW_TOLERANCE, ZOOM_MAX, ZOOM_MIN, type ZoomReadout, canZoomIn, canZoomOut, decidePaletteRow, easeStandard, fitInsets, fitViewbox, formatNumber, formatPercent, freeRectCenter, interpolateViewbox, legendStartsOpen, placeTooltip, sameView, shellSizesFromTokens, zoomReadout };
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { d as Size, c as ShellHost, P as PaletteRow, I as Insets, B as Bounds, S as ScaledViewbox, b as Point } from './types-B6NfkiUG.js';
|
|
2
|
+
export { C as CHROME_ISLANDS, a as ChromeIsland, V as ViewportState, Z as ZoomTarget } from './types-B6NfkiUG.js';
|
|
3
|
+
import { d as ShellMessages } from './keymap-DORzHnK-.js';
|
|
4
|
+
export { C as ChromeKey, K as KeyAction, a as KeyContext, b as KeyDecision, c as KeyLike, N as NavigatorLike, P as Platform, S as ShellMessageOverrides, T as TEXT_FIELD_SELECTOR, e as ariaKeyShortcuts, f as ariaKeyShortcutsFromLabel, g as decideKey, h as defaultMessages, i as detectPlatform, j as isUndoRedoKey, m as matchChromeKey, r as resolveMessages, s as shortcutLabel } from './keymap-DORzHnK-.js';
|
|
5
|
+
export { A as Appearance, a as AppearanceAttributes, C as ColorThemeSetting, K as Kontrast, R as REDUCED_MOTION_CLASS, T as Theme, b as appearanceAttributes, c as appearanceFromBodyClasses, d as appearanceFromColorThemeKind, r as reducedMotionFromBodyClasses, e as requestedAppearance, f as resolveAppearance } from './appearance-D3FUcbu6.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The shell dimensions of the design system (guide §5.1, `shell` group of
|
|
9
|
+
* `cd-tokens.generated.json`), keyed by a readable name. Scripts use `SHELL`; CSS uses the
|
|
10
|
+
* `var(--cd-shell-*)` variables the consumer vendors. A drift test pins `SHELL` to the token JSON.
|
|
11
|
+
*/
|
|
12
|
+
declare const SHELL_TOKENS: {
|
|
13
|
+
readonly edge: "--cd-shell-rand";
|
|
14
|
+
readonly gap: "--cd-shell-luecke";
|
|
15
|
+
readonly row: "--cd-shell-zeile";
|
|
16
|
+
readonly target: "--cd-shell-ziel";
|
|
17
|
+
readonly entry: "--cd-shell-eintrag";
|
|
18
|
+
readonly free: "--cd-shell-frei";
|
|
19
|
+
readonly menuMinWidth: "--cd-shell-menue-breite";
|
|
20
|
+
readonly inspectorWidth: "--cd-shell-inspector-breite";
|
|
21
|
+
readonly fitInsetTop: "--cd-shell-einpassen-oben";
|
|
22
|
+
readonly fitInsetTopSecondRow: "--cd-shell-einpassen-oben-schmal";
|
|
23
|
+
readonly fitInsetSide: "--cd-shell-einpassen-seite";
|
|
24
|
+
readonly fitInsetBottom: "--cd-shell-einpassen-unten";
|
|
25
|
+
readonly fitInsetBottomWeb: "--cd-shell-einpassen-unten-web";
|
|
26
|
+
readonly narrow: "--cd-shell-schmal";
|
|
27
|
+
readonly veryNarrow: "--cd-shell-sehr-schmal";
|
|
28
|
+
readonly low: "--cd-shell-niedrig";
|
|
29
|
+
readonly layerIslands: "--cd-shell-ebene-inseln";
|
|
30
|
+
readonly layerInspector: "--cd-shell-ebene-inspector";
|
|
31
|
+
readonly layerTopRow: "--cd-shell-ebene-reihe";
|
|
32
|
+
readonly layerPopups: "--cd-shell-ebene-aufklapper";
|
|
33
|
+
readonly layerDialog: "--cd-shell-ebene-dialog";
|
|
34
|
+
readonly layerToast: "--cd-shell-ebene-toast";
|
|
35
|
+
};
|
|
36
|
+
type ShellSizeName = keyof typeof SHELL_TOKENS;
|
|
37
|
+
type ShellSizes = Readonly<Record<ShellSizeName, number>>;
|
|
38
|
+
/** Pixels (layers: plain z-index numbers), equal to `parseFloat` of the token JSON values. */
|
|
39
|
+
declare const SHELL: ShellSizes;
|
|
40
|
+
/**
|
|
41
|
+
* Reads the `shell` group of `cd-tokens.generated.json` (`{ "--cd-shell-rand": "12px", … }`) into
|
|
42
|
+
* numbers. Throws when a size of `SHELL_TOKENS` is missing or not a number.
|
|
43
|
+
*/
|
|
44
|
+
declare function shellSizesFromTokens(shellGroup: Readonly<Record<string, string>>): ShellSizes;
|
|
45
|
+
|
|
46
|
+
/** Fit never zooms above 100 % (guide §5.9). */
|
|
47
|
+
declare const MAX_FIT_ZOOM = 1;
|
|
48
|
+
/** Duration of the explicit, animated fit (`--cd-motion-base`). */
|
|
49
|
+
declare const FIT_ANIMATION_MS = 250;
|
|
50
|
+
/**
|
|
51
|
+
* The viewbox that shows `bounds` centred in the free rectangle (`container` minus `insets`) at the
|
|
52
|
+
* largest scale that fits, capped at `maxZoom`. The box has the container's aspect ratio, so
|
|
53
|
+
* diagram-js's `canvas.viewbox(box)` sets exactly this view. Returns `undefined` when the container
|
|
54
|
+
* is 0 × 0 or `bounds` has neither width nor height; a zero width or height scales by the other
|
|
55
|
+
* dimension. Where the insets leave no free room on an axis (a container lower or narrower than
|
|
56
|
+
* its chrome), that axis centres in the whole container instead.
|
|
57
|
+
*/
|
|
58
|
+
declare function fitViewbox(bounds: Bounds, container: Size, insets: Insets, maxZoom?: number): ScaledViewbox | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* The centre of the free rectangle (`container` minus `insets`) in pixels from the container's top
|
|
61
|
+
* left, where `fitViewbox` centres the content and keyboard creation places an element (guide
|
|
62
|
+
* §5.5). An axis the insets leave no room on centres in the whole container, as in `fitViewbox`.
|
|
63
|
+
* Returns `undefined` when the container is 0 × 0.
|
|
64
|
+
*/
|
|
65
|
+
declare function freeRectCenter(container: Size, insets: Insets): Point | undefined;
|
|
66
|
+
/** What is open or shown when a fit runs (guide §5.9: every fit counts what is open right now). */
|
|
67
|
+
interface FitLayout {
|
|
68
|
+
container: Size;
|
|
69
|
+
host: ShellHost;
|
|
70
|
+
paletteRow: PaletteRow;
|
|
71
|
+
/** The problems pill is shown (only the web app reserves room for it). */
|
|
72
|
+
problemsVisible: boolean;
|
|
73
|
+
/** Rendered width of the open legend; `undefined` when it is closed or the modeler has none. */
|
|
74
|
+
legendWidth?: number | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* The open inspector; `undefined` when closed. Above `SHELL.narrow` it is the side panel; up to it
|
|
77
|
+
* a bottom sheet whose top edge (`sheetTop`, px from the container top) must be measured.
|
|
78
|
+
*/
|
|
79
|
+
inspector?: {
|
|
80
|
+
sheetTop?: number | undefined;
|
|
81
|
+
} | undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The fit insets of guide §5.9: top 88 (second row 140), sides 24, bottom 64 (web app with pill 92),
|
|
85
|
+
* right 324 with an open side inspector, left 12 + legend width + 24 with an open legend above
|
|
86
|
+
* `SHELL.veryNarrow`, bottom = distance to the sheet's top edge + 12 with an open bottom sheet.
|
|
87
|
+
*/
|
|
88
|
+
declare function fitInsets(layout: FitLayout): Insets;
|
|
89
|
+
/** Relative tolerance of `sameView` (diagram-js rounds the transform to three decimals). */
|
|
90
|
+
declare const VIEW_TOLERANCE = 0.001;
|
|
91
|
+
/**
|
|
92
|
+
* True when two views are the same within `tolerance`: the scale and the translation in screen
|
|
93
|
+
* pixels (`x · scale`, `y · scale`) agree relative to `max(1, |value|)`. Width and height are
|
|
94
|
+
* ignored, so a container resize is never a difference.
|
|
95
|
+
*/
|
|
96
|
+
declare function sameView(a: Pick<ScaledViewbox, "x" | "y" | "scale">, b: Pick<ScaledViewbox, "x" | "y" | "scale">, tolerance?: number): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The view at `progress` (0…1) between two viewboxes of the same container: the scale is
|
|
99
|
+
* interpolated in log space, the centre linearly. Used per animation frame by the animated fit.
|
|
100
|
+
*/
|
|
101
|
+
declare function interpolateViewbox(from: ScaledViewbox, to: ScaledViewbox, progress: number): ScaledViewbox;
|
|
102
|
+
/** `--cd-ease`, `cubic-bezier(0.4, 0, 0.2, 1)`, as a function of time 0…1. */
|
|
103
|
+
declare function easeStandard(progress: number): number;
|
|
104
|
+
|
|
105
|
+
/** diagram-js `ZoomScroll` RANGE (not configurable): 20 % … 400 %. */
|
|
106
|
+
declare const ZOOM_MIN = 0.2;
|
|
107
|
+
declare const ZOOM_MAX = 4;
|
|
108
|
+
/** Zoom out is disabled at 20 % and below (`zoom <= 0.2 · 1.001`), so it never zooms in. */
|
|
109
|
+
declare function canZoomOut(zoom: number): boolean;
|
|
110
|
+
/** Zoom in is disabled at 400 % (`zoom >= 4 · 0.999`). */
|
|
111
|
+
declare function canZoomIn(zoom: number): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* A zoom as a whole percentage in the UI language: `Intl.NumberFormat(lang, { style: "percent",
|
|
114
|
+
* maximumFractionDigits: 0 })`, so English "86%", German "86 %" (with U+00A0).
|
|
115
|
+
*/
|
|
116
|
+
declare function formatPercent(zoom: number, lang: string): string;
|
|
117
|
+
/** Any other number in the chrome ("2 problems"), via `Intl.NumberFormat(lang)`. */
|
|
118
|
+
declare function formatNumber(value: number, lang: string): string;
|
|
119
|
+
interface ZoomReadout {
|
|
120
|
+
/** "86%" */
|
|
121
|
+
text: string;
|
|
122
|
+
/** "Zoom to 100% (now 86%)" */
|
|
123
|
+
ariaLabel: string;
|
|
124
|
+
canZoomOut: boolean;
|
|
125
|
+
canZoomIn: boolean;
|
|
126
|
+
}
|
|
127
|
+
/** Everything the zoom panel shows for a zoom: readout text, its accessible name and the limits. */
|
|
128
|
+
declare function zoomReadout(zoom: number, lang: string, messages: Pick<ShellMessages, "zoomToActualNow">): ZoomReadout;
|
|
129
|
+
|
|
130
|
+
/** Free space the top row needs between the palette and a neighbouring island (guide §5.15). */
|
|
131
|
+
declare const PALETTE_CLEARANCE = 16;
|
|
132
|
+
/** Hover delay of the chrome tooltip, equal to diagram-js `HoverTooltip` `SHOW_DELAY`. */
|
|
133
|
+
declare const TOOLTIP_DELAY_MS = 300;
|
|
134
|
+
/** The status box for an invalid file shows after this long without a valid import (guide §5.12). */
|
|
135
|
+
declare const INVALID_STATUS_DELAY_MS = 800;
|
|
136
|
+
interface PaletteRowInput {
|
|
137
|
+
containerWidth: number;
|
|
138
|
+
/**
|
|
139
|
+
* The palette's intrinsic border-box width (`scrollWidth` plus its borders), so the decision does
|
|
140
|
+
* not flip once the second row clips it.
|
|
141
|
+
*/
|
|
142
|
+
paletteWidth: number;
|
|
143
|
+
/** Right edge of the menu trigger, relative to the shell root. */
|
|
144
|
+
menuRight: number;
|
|
145
|
+
/** Left edge of the primary action (web app), relative to the shell root; absent in IDE hosts. */
|
|
146
|
+
primaryLeft?: number | undefined;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* `second-row` when the container is at most `SHELL.narrow` wide or a centred palette would keep
|
|
150
|
+
* less than `PALETTE_CLEARANCE` to the menu trigger (or the primary action); `top` otherwise.
|
|
151
|
+
*/
|
|
152
|
+
declare function decidePaletteRow(input: PaletteRowInput): PaletteRow;
|
|
153
|
+
/** The legend starts open unless the container is at most `SHELL.low` high (guide §5.10). */
|
|
154
|
+
declare function legendStartsOpen(containerHeight: number): boolean;
|
|
155
|
+
interface TooltipPlacementInput {
|
|
156
|
+
/** The hovered or focused control, relative to the shell root (horizontal centre). */
|
|
157
|
+
anchor: Bounds;
|
|
158
|
+
/** The island the control sits in, relative to the shell root (vertical placement). */
|
|
159
|
+
island: Bounds;
|
|
160
|
+
tooltip: Size;
|
|
161
|
+
container: Size;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The tooltip's top-left corner relative to the shell root (guide §5.6): below an island in the
|
|
165
|
+
* upper half (`island.bottom + 8`: top row 60, second row 112), above one in the lower half
|
|
166
|
+
* (`island.top − 8 − height`: bottom row ends at `bottom: 60px`); centred on the anchor, shifted to
|
|
167
|
+
* keep `SHELL.edge` to the container's sides.
|
|
168
|
+
*/
|
|
169
|
+
declare function placeTooltip(input: TooltipPlacementInput): Point;
|
|
170
|
+
|
|
171
|
+
export { Bounds, FIT_ANIMATION_MS, type FitLayout, INVALID_STATUS_DELAY_MS, Insets, MAX_FIT_ZOOM, PALETTE_CLEARANCE, PaletteRow, type PaletteRowInput, Point, SHELL, SHELL_TOKENS, ScaledViewbox, ShellHost, ShellMessages, type ShellSizeName, type ShellSizes, Size, TOOLTIP_DELAY_MS, type TooltipPlacementInput, VIEW_TOLERANCE, ZOOM_MAX, ZOOM_MIN, type ZoomReadout, canZoomIn, canZoomOut, decidePaletteRow, easeStandard, fitInsets, fitViewbox, formatNumber, formatPercent, freeRectCenter, interpolateViewbox, legendStartsOpen, placeTooltip, sameView, shellSizesFromTokens, zoomReadout };
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHROME_ISLANDS,
|
|
3
|
+
FIT_ANIMATION_MS,
|
|
4
|
+
INVALID_STATUS_DELAY_MS,
|
|
5
|
+
MAX_FIT_ZOOM,
|
|
6
|
+
PALETTE_CLEARANCE,
|
|
7
|
+
REDUCED_MOTION_CLASS,
|
|
8
|
+
SHELL,
|
|
9
|
+
SHELL_TOKENS,
|
|
10
|
+
TEXT_FIELD_SELECTOR,
|
|
11
|
+
TOOLTIP_DELAY_MS,
|
|
12
|
+
VIEW_TOLERANCE,
|
|
13
|
+
ZOOM_MAX,
|
|
14
|
+
ZOOM_MIN,
|
|
15
|
+
appearanceAttributes,
|
|
16
|
+
appearanceFromBodyClasses,
|
|
17
|
+
appearanceFromColorThemeKind,
|
|
18
|
+
ariaKeyShortcuts,
|
|
19
|
+
ariaKeyShortcutsFromLabel,
|
|
20
|
+
canZoomIn,
|
|
21
|
+
canZoomOut,
|
|
22
|
+
decideKey,
|
|
23
|
+
decidePaletteRow,
|
|
24
|
+
defaultMessages,
|
|
25
|
+
detectPlatform,
|
|
26
|
+
easeStandard,
|
|
27
|
+
fitInsets,
|
|
28
|
+
fitViewbox,
|
|
29
|
+
formatNumber,
|
|
30
|
+
formatPercent,
|
|
31
|
+
freeRectCenter,
|
|
32
|
+
interpolateViewbox,
|
|
33
|
+
isUndoRedoKey,
|
|
34
|
+
legendStartsOpen,
|
|
35
|
+
matchChromeKey,
|
|
36
|
+
placeTooltip,
|
|
37
|
+
reducedMotionFromBodyClasses,
|
|
38
|
+
requestedAppearance,
|
|
39
|
+
resolveAppearance,
|
|
40
|
+
resolveMessages,
|
|
41
|
+
sameView,
|
|
42
|
+
shellSizesFromTokens,
|
|
43
|
+
shortcutLabel,
|
|
44
|
+
zoomReadout
|
|
45
|
+
} from "./chunk-5JSZ4VGA.js";
|
|
46
|
+
export {
|
|
47
|
+
CHROME_ISLANDS,
|
|
48
|
+
FIT_ANIMATION_MS,
|
|
49
|
+
INVALID_STATUS_DELAY_MS,
|
|
50
|
+
MAX_FIT_ZOOM,
|
|
51
|
+
PALETTE_CLEARANCE,
|
|
52
|
+
REDUCED_MOTION_CLASS,
|
|
53
|
+
SHELL,
|
|
54
|
+
SHELL_TOKENS,
|
|
55
|
+
TEXT_FIELD_SELECTOR,
|
|
56
|
+
TOOLTIP_DELAY_MS,
|
|
57
|
+
VIEW_TOLERANCE,
|
|
58
|
+
ZOOM_MAX,
|
|
59
|
+
ZOOM_MIN,
|
|
60
|
+
appearanceAttributes,
|
|
61
|
+
appearanceFromBodyClasses,
|
|
62
|
+
appearanceFromColorThemeKind,
|
|
63
|
+
ariaKeyShortcuts,
|
|
64
|
+
ariaKeyShortcutsFromLabel,
|
|
65
|
+
canZoomIn,
|
|
66
|
+
canZoomOut,
|
|
67
|
+
decideKey,
|
|
68
|
+
decidePaletteRow,
|
|
69
|
+
defaultMessages,
|
|
70
|
+
detectPlatform,
|
|
71
|
+
easeStandard,
|
|
72
|
+
fitInsets,
|
|
73
|
+
fitViewbox,
|
|
74
|
+
formatNumber,
|
|
75
|
+
formatPercent,
|
|
76
|
+
freeRectCenter,
|
|
77
|
+
interpolateViewbox,
|
|
78
|
+
isUndoRedoKey,
|
|
79
|
+
legendStartsOpen,
|
|
80
|
+
matchChromeKey,
|
|
81
|
+
placeTooltip,
|
|
82
|
+
reducedMotionFromBodyClasses,
|
|
83
|
+
requestedAppearance,
|
|
84
|
+
resolveAppearance,
|
|
85
|
+
resolveMessages,
|
|
86
|
+
sameView,
|
|
87
|
+
shellSizesFromTokens,
|
|
88
|
+
shortcutLabel,
|
|
89
|
+
zoomReadout
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|