@umriss-ui/dashboard 0.0.0-stage → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changes to `@umriss-ui/dashboard`
2
+
3
+ This document describes what changes for **callers** of the package: exports,
4
+ props, behaviour.
5
+
6
+ **The numbers.** The middle digit rises when something is added; the last one
7
+ when something is repaired. As long as the first digit is `0`, no number
8
+ promises compatibility — which is why a rule stronger than the figure applies as
9
+ well: **whatever changes existing behaviour stands under a heading "Changed" of
10
+ its own**, no matter which digit rose. Whoever reads only one section before an
11
+ upgrade reads that one.
12
+
13
+ The dashboard joins the other five packages' one number at its first version: they are released together, and a package without a change of its own still gets a heading for it.
14
+
15
+ ## 0.40.0 – The first version (Oct. 2026)
16
+
17
+ ### Added
18
+
19
+ - **`useDashboard`** takes the arrangement the application gives a reader (`defaultView`) and the one the reader kept (`initialView`, read once), and hands back `Dashboard`, `WidgetType`, the view, `setView` and the editing actions. A view is a sequence of widgets, each with its type, its size and its options.
20
+ - **`WidgetType`** declares a widget type as JSX: its name, a sentence, the sizes it offers with the columns and rows each takes, the size to start from, its default options, an optional form for them and its content as a function of the size and the options. The options type is inferred from the default options. **`HeadingType`** is a built-in type that divides the dashboard with a title across the full width.
21
+ - **Twelve columns at every width.** The widgets are packed in the stored order, each at the first place at or after the one before it; the room left behind stays free. Under a maximum width the dashboard stops growing, and below the width at which a column would be too narrow to read it is a list in the same order. Decided on the dashboard's own width, so the server's markup is right.
22
+ - **Editing as a draft.** "Edit" opens the mode; one menu on every widget moves, sizes, sets the options and removes it; the head takes the arrow keys, Home and End, and is the grip for a pointer drag; "Add widget" opens the catalogue. Every change is a step of the draft with Undo, Redo and Reset to the default; "Done" reports the whole arrangement once through `onViewChange`, "Cancel" puts it back, and `onDraftChange` says while a draft is open. `allowEdit` forbids it. Every change is announced, in English and German.
23
+ - **A kept arrangement is read leniently:** a widget of a type that is gone falls out, a size its type no longer offers falls back to its starting size. A widget whose content throws keeps its frame, says so in one sentence and can still be moved and removed.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roman Händler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,101 @@
1
- # Temporary Holding Version
1
+ # @umriss-ui/dashboard
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A React dashboard for the start screen of a data-dense application: widgets a
4
+ developer declares and a reader arranges. The arrangement is a sequence, each
5
+ widget at one of the sizes its type offers, packed in the stored order on
6
+ twelve columns that never change in number. The reader edits it in a mode, as a
7
+ draft with undo that is kept or discarded as a whole; the application keeps the
8
+ result and supplies the arrangement a reader starts from.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pnpm add @umriss-ui/dashboard @umriss-ui/core
14
+ ```
15
+
16
+ `@umriss-ui/core` is a **peer dependency**: the dashboard takes its styling
17
+ regime, its frames, menus and drawer, its wording and its announcements from
18
+ there. React 19 or newer as a peer as well. The dashboard depends on `core`
19
+ alone; nothing depends on the dashboard, and it takes no chart, table or
20
+ schedule - what stands in a widget is the application's.
21
+
22
+ The package ships ES2022 syntax; an application that serves older browsers
23
+ transpiles it itself.
24
+
25
+ Every entry begins with `"use client"`, the directive a framework with Server
26
+ Components reads to treat a module as client code. The dashboard renders to a
27
+ string on the server and in a test, without a layout engine.
28
+
29
+ ## Declared like a table
30
+
31
+ `useDashboard` takes the arrangement the application gives a reader and hands
32
+ back `Dashboard` and `WidgetType`. A widget type is JSX: its name, a sentence,
33
+ the sizes it offers with the columns (of twelve) and rows each takes, the size
34
+ to start from, its default options, and its content as a function of the size
35
+ and the options. The options type is inferred from the default options.
36
+
37
+ ```tsx
38
+ import { Stat } from "@umriss-ui/core";
39
+ import { useDashboard } from "@umriss-ui/dashboard";
40
+
41
+ const { Dashboard, WidgetType } = useDashboard({
42
+ defaultView: { widgets: [{ id: "a", type: "figure", size: "small", options: { name: "Orders" } }] },
43
+ initialView: kept,
44
+ onViewChange: (view) => save(view),
45
+ });
46
+
47
+ <Dashboard aria-label="Dispatch, today">
48
+ <WidgetType
49
+ name="figure"
50
+ label="Figure"
51
+ sentence="One number."
52
+ sizes={{ small: { columns: 3, rows: 2 }, medium: { columns: 4, rows: 3 } }}
53
+ start="small"
54
+ defaultOptions={{ name: "Orders" }}
55
+ >
56
+ {({ options }) => <Stat label={options.name} value={1284} />}
57
+ </WidgetType>
58
+ </Dashboard>;
59
+ ```
60
+
61
+ - **A sequence, never coordinates (ADR-0064).** One stored arrangement is right
62
+ at every width. A widget that does not fit its row starts the next, and the
63
+ room it leaves stays free. Below the width at which a column would be too
64
+ narrow to read, the dashboard is a list in the same order. Dense packing,
65
+ stacks of widgets sharing a place and a layout per breakpoint are not built.
66
+ - **Sizes have names.** `small`, `medium`, `wide`, `tall`, `large` and `full`,
67
+ worded in English and German by the library; a type offers some of them. The
68
+ height follows the size, never the content.
69
+ - **Editing is a mode and a draft.** "Edit" opens it; every change is a step
70
+ that can be undone and redone; "Done" reports the whole arrangement once
71
+ through `onViewChange`, "Cancel" puts it back, "Reset" steps to the default.
72
+ `onDraftChange` tells the application while a draft is open, to warn before
73
+ navigation.
74
+ - **Every action without a drag.** One menu on every widget's head moves,
75
+ sizes, sets the options and removes; the head is also the grip for a pointer
76
+ and takes the arrow keys. Every change is announced.
77
+ - **A kept arrangement is read leniently.** A widget of a type that is gone
78
+ falls out, a size a type no longer offers falls back to its starting size.
79
+ A widget whose content throws keeps its frame and says so in a sentence.
80
+ - **The application's, not the library's:** where an arrangement is stored,
81
+ which default a role gets, several dashboards, sharing, and what stands in a
82
+ widget.
83
+
84
+ ## Documentation
85
+
86
+ The demo is the documentation:
87
+ <https://romanhaendler.github.io/umriss-ui/dashboard/>, or locally
88
+ `pnpm dev:dashboard` (port 4178). Its pages show running examples with their
89
+ source and the props table generated from `src/`, each prop linked to the
90
+ examples that show it; the scenario walks through a dispatcher's start screen
91
+ at other widths, while it is rearranged, when something goes wrong and what the
92
+ application governs. An API index lists every export, one search finds across
93
+ all six packages, and the header switches the examples to German. What is not
94
+ built yet and what never will be stands in ADR-0064. The vocabulary - dashboard,
95
+ widget, widget type, widget size, options, catalogue - stands in `CONTEXT.md`
96
+ under "Dashboards". Every export is listed in [API.md](API.md).
97
+
98
+ For a coding agent the demo stands as one Markdown file inside the installed
99
+ package, `docs/llms-full.md`, pinned to that version. Each page also has a
100
+ Markdown twin online; for the latest version:
101
+ <https://romanhaendler.github.io/umriss-ui/dashboard/llms.txt>.
@@ -0,0 +1 @@
1
+ @layer umriss.tokens,umriss.base,umriss.components;@layer umriss.components{._root_xjsd2_17,._root_xjsd2_17:before,._root_xjsd2_17:after{box-sizing:border-box}._bar_xjsd2_34,._bar_xjsd2_34:before,._bar_xjsd2_34:after{box-sizing:border-box}._mode_xjsd2_43,._mode_xjsd2_43:before,._mode_xjsd2_43:after{box-sizing:border-box}._head_xjsd2_58,._head_xjsd2_58:before,._head_xjsd2_58:after{box-sizing:border-box}._editingHead_xjsd2_68,._editingHead_xjsd2_68:before,._editingHead_xjsd2_68:after{box-sizing:border-box}._grip_xjsd2_76,._grip_xjsd2_76:before,._grip_xjsd2_76:after{box-sizing:border-box}._title_xjsd2_81,._title_xjsd2_81:before,._title_xjsd2_81:after{box-sizing:border-box}._size_xjsd2_92,._size_xjsd2_92:before,._size_xjsd2_92:after{box-sizing:border-box}._item_xjsd2_100,._item_xjsd2_100:before,._item_xjsd2_100:after{box-sizing:border-box}._placeholder_xjsd2_110,._placeholder_xjsd2_110:before,._placeholder_xjsd2_110:after{box-sizing:border-box}._grid_xjsd2_116,._grid_xjsd2_116:before,._grid_xjsd2_116:after{box-sizing:border-box}._headingRow_xjsd2_133,._headingRow_xjsd2_133:before,._headingRow_xjsd2_133:after{box-sizing:border-box}._headingHead_xjsd2_138,._headingHead_xjsd2_138:before,._headingHead_xjsd2_138:after{box-sizing:border-box}._heading_xjsd2_133,._heading_xjsd2_133:before,._heading_xjsd2_133:after{box-sizing:border-box}._body_xjsd2_163,._body_xjsd2_163:before,._body_xjsd2_163:after{box-sizing:border-box}._options_xjsd2_189,._options_xjsd2_189:before,._options_xjsd2_189:after{box-sizing:border-box}._catalogue_xjsd2_194,._catalogue_xjsd2_194:before,._catalogue_xjsd2_194:after{box-sizing:border-box}._entry_xjsd2_202,._entry_xjsd2_202:before,._entry_xjsd2_202:after{box-sizing:border-box}._footprint_xjsd2_209,._footprint_xjsd2_209:before,._footprint_xjsd2_209:after{box-sizing:border-box}._entryText_xjsd2_221,._entryText_xjsd2_221:before,._entryText_xjsd2_221:after{box-sizing:border-box}._root_xjsd2_17{container-type:inline-size;width:100%;padding:0 var(--u-space-4) var(--u-space-4);border-radius:var(--u-radius-lg);corner-shape:squircle;--row-unit: var(--u-dashboard-row, 50px);--gap: var(--u-dashboard-gap, 12px)}._root_xjsd2_17[data-editing]{box-shadow:inset 0 0 0 2px var(--u-color-accent);outline:2px solid transparent;outline-offset:-2px}._bar_xjsd2_34{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--u-space-2);padding-block:var(--u-space-3)}._mode_xjsd2_43{margin-inline-end:auto;padding:5px var(--u-space-2);border-radius:var(--u-radius-xs);corner-shape:squircle;background:var(--u-color-accent);color:var(--u-color-on-accent);font-family:var(--u-font-mono);font-size:var(--u-text-xs);font-weight:var(--u-weight-medium);line-height:var(--u-leading-none);letter-spacing:var(--u-tracking-caps);text-transform:uppercase}._head_xjsd2_58{display:flex;align-items:center;gap:6px;min-width:0;min-height:calc(var(--u-control-height) + 2px);padding:var(--u-space-2) var(--u-space-2) 0 var(--u-space-3);border-radius:var(--u-radius-lg) var(--u-radius-lg) 0 0;corner-shape:squircle}._editingHead_xjsd2_68{padding-block-end:var(--u-space-1);background:var(--u-color-surface-pressed);touch-action:none;user-select:none;cursor:grab}._grip_xjsd2_76{flex:none;color:var(--u-color-text-muted)}._title_xjsd2_81{flex:1 1 auto;min-width:0;margin:0;overflow:hidden;font-size:var(--u-text-sm);font-weight:var(--u-weight-medium);white-space:nowrap;text-overflow:ellipsis}._size_xjsd2_92{font-family:var(--u-font-mono);font-size:var(--u-text-xs);line-height:var(--u-leading-none);color:var(--u-color-text-muted);white-space:nowrap}._item_xjsd2_100[data-dragging]{position:relative;z-index:5;box-shadow:var(--u-shadow-overlay)}._item_xjsd2_100[data-dragging] ._editingHead_xjsd2_68{cursor:grabbing}._placeholder_xjsd2_110{border:2px dashed var(--u-color-accent);border-radius:var(--u-radius-lg);corner-shape:squircle;background:var(--u-color-accent-subtle)}._grid_xjsd2_116{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));grid-auto-rows:var(--row-unit);gap:var(--gap);max-width:var(--u-dashboard-max-width, 1560px)}._item_xjsd2_100{grid-column:var(--column) / span var(--columns);grid-row:var(--row) / span var(--rows);min-height:0;min-width:0}._headingRow_xjsd2_133{display:flex;align-items:stretch}._headingHead_xjsd2_138{flex:1 1 auto;align-items:flex-end;padding:0 var(--u-space-2) 0 2px;border-radius:var(--u-radius-md);corner-shape:squircle}._headingRow_xjsd2_133[data-editing] ._headingHead_xjsd2_138{align-items:center;padding-inline-start:var(--u-space-3);padding-block-end:0}._heading_xjsd2_133{flex:1 1 auto;min-width:0;margin:0;padding-block-end:calc(var(--u-space-1) + 2px);font-size:var(--u-text-lg)}._headingRow_xjsd2_133[data-editing] ._heading_xjsd2_133{padding-block-end:0}._body_xjsd2_163{flex:1 1 auto;min-height:0;padding:2px var(--u-space-3) 10px;overflow:hidden}._item_xjsd2_100[data-editing] ._body_xjsd2_163{padding-block-start:var(--u-space-2)}@container (max-width: 755px){._grid_xjsd2_116{grid-template-columns:minmax(0,1fr);grid-auto-rows:auto}._item_xjsd2_100{grid-column:1;grid-row:auto;order:var(--order, 0);height:calc(var(--rows) * var(--row-unit) + (var(--rows) - 1) * var(--gap))}}._options_xjsd2_189{min-width:16rem;padding:var(--u-space-3)}._catalogue_xjsd2_194{list-style:none;margin:0;padding:0;display:grid;gap:var(--u-space-3, 12px)}._entry_xjsd2_202{display:grid;grid-template-columns:4.5rem 1fr auto;align-items:center;gap:var(--u-space-3, 12px)}._footprint_xjsd2_209{aspect-ratio:3 / 1;border-radius:2px;corner-shape:squircle;box-shadow:var(--u-edge)}._footprint_xjsd2_209>span{box-sizing:border-box}._footprint_xjsd2_209>span{display:block;background:var(--u-color-accent, currentColor);opacity:.5}._entryText_xjsd2_221{display:grid}}
@@ -0,0 +1,426 @@
1
+ "use client";
2
+ import "./dashboard.css";
3
+ import { Component as ye, useState as Z, useRef as E, useLayoutEffect as de, useMemo as oe, Children as ze, isValidElement as ie, useEffect as ne, useSyncExternalStore as ce, createElement as Me } from "react";
4
+ import { useWording as ae, FormField as ve, Input as Ce, Button as L, Text as K, GripGlyph as le, Heading as Se, Card as Ne, Drawer as ke, ModalHeader as je, ModalBody as He, Popover as Ee, announce as $, Menu as Oe, MenuItem as B, MenuSeparator as se } from "@umriss-ui/core";
5
+ import { jsx as a, jsxs as H, Fragment as re } from "react/jsx-runtime";
6
+ const Re = (s, n) => (d) => {
7
+ const c = d.widgets.findIndex((t) => t.id === s), C = Math.max(0, Math.min(d.widgets.length - 1, n));
8
+ if (c < 0 || c === C) return d;
9
+ const y = [...d.widgets];
10
+ return y.splice(C, 0, ...y.splice(c, 1)), { ...d, widgets: y };
11
+ }, Te = (s, n) => (d) => d.widgets.some((c) => c.id === s && c.size !== n) ? { ...d, widgets: d.widgets.map((c) => c.id === s ? { ...c, size: n } : c) } : d, De = (s) => (n) => n.widgets.some((d) => d.id === s) ? { ...n, widgets: n.widgets.filter((d) => d.id !== s) } : n, Ie = (s, n) => {
12
+ let d = 1;
13
+ for (; s.widgets.some((c) => c.id === `${n}-${d}`); ) d++;
14
+ return `${n}-${d}`;
15
+ }, Le = (s) => (n) => ({ ...n, widgets: [...n.widgets, s] }), $e = (s, n) => (d) => ({ ...d, widgets: d.widgets.map((c) => c.id === s ? { ...c, options: n } : c) }), Ae = "_root_xjsd2_17", Fe = "_bar_xjsd2_34", Pe = "_mode_xjsd2_43", Be = "_head_xjsd2_58", Ue = "_editingHead_xjsd2_68", Ve = "_grip_xjsd2_76", Ge = "_title_xjsd2_81", We = "_size_xjsd2_92", Ye = "_item_xjsd2_100", Xe = "_placeholder_xjsd2_110", Je = "_grid_xjsd2_116", Ke = "_headingRow_xjsd2_133", qe = "_headingHead_xjsd2_138", Qe = "_heading_xjsd2_133", Ze = "_body_xjsd2_163", et = "_options_xjsd2_189", tt = "_catalogue_xjsd2_194", nt = "_entry_xjsd2_202", st = "_footprint_xjsd2_209", rt = "_entryText_xjsd2_221", _ = {
16
+ root: Ae,
17
+ bar: Fe,
18
+ mode: Pe,
19
+ head: Be,
20
+ editingHead: Ue,
21
+ grip: Ve,
22
+ title: Ge,
23
+ size: We,
24
+ item: Ye,
25
+ placeholder: Xe,
26
+ grid: Je,
27
+ headingRow: Ke,
28
+ headingHead: qe,
29
+ heading: Qe,
30
+ body: Ze,
31
+ options: et,
32
+ catalogue: tt,
33
+ entry: nt,
34
+ footprint: st,
35
+ entryText: rt
36
+ };
37
+ class ot extends ye {
38
+ state = { failed: !1 };
39
+ static getDerivedStateFromError() {
40
+ return { failed: !0 };
41
+ }
42
+ componentDidUpdate(n) {
43
+ this.state.failed && (n.size !== this.props.size || n.options !== this.props.options) && this.setState({ failed: !1 });
44
+ }
45
+ render() {
46
+ return this.state.failed ? this.props.fallback : this.props.children;
47
+ }
48
+ }
49
+ function ge(s, n) {
50
+ const d = /* @__PURE__ */ new Set();
51
+ return {
52
+ ...s,
53
+ widgets: s.widgets.flatMap((c) => {
54
+ const C = n.get(c.type);
55
+ return !C || d.has(c.id) ? [] : (d.add(c.id), [C.sizes[c.size] ? c : { ...c, size: C.start }]);
56
+ })
57
+ };
58
+ }
59
+ function me(s, n) {
60
+ const d = /* @__PURE__ */ new Set(), c = (y, t, w, i) => {
61
+ for (let h = y; h < y + i; h++) for (let m = t; m < t + w; m++) if (d.has(h * n + m)) return !1;
62
+ return !0;
63
+ };
64
+ let C = 0;
65
+ return s.map((y) => {
66
+ const t = Math.min(y.columns, n);
67
+ for (let w = C; ; w++) {
68
+ const i = Math.floor(w / n), h = w % n;
69
+ if (!(h + t > n || !c(i, h, t, y.rows))) {
70
+ for (let m = i; m < i + y.rows; m++) for (let p = h; p < h + t; p++) d.add(m * n + p);
71
+ return C = w, { column: h, row: i, columns: t, rows: y.rows };
72
+ }
73
+ }
74
+ });
75
+ }
76
+ function at(s, n, d, c, C, { cell: y, unit: t, gap: w }) {
77
+ let i = d, h = 1 / 0;
78
+ for (let m = 0; m < s.length; m++) {
79
+ const p = [...s];
80
+ p.splice(m, 0, ...p.splice(d, 1));
81
+ const T = me(p, n)[m], k = (T.column * (y + w) - c) ** 2 + (T.row * (t + w) - C) ** 2;
82
+ k < h && (h = k, i = m);
83
+ }
84
+ return i;
85
+ }
86
+ const dt = { 3: "¼", 4: "⅓", 6: "½", 8: "⅔", 9: "¾" }, it = ({ columns: s }) => s >= 12 ? "" : dt[s] ?? `${s}/12`;
87
+ function ct({ grid: s, cards: n, shown: d, drop: c, cancelled: C }) {
88
+ const [y, t] = Z(null), w = E(null), i = E({}), h = E(/* @__PURE__ */ new Map()), m = E(null), p = E({ update: () => {
89
+ }, finish: () => {
90
+ } }), T = () => {
91
+ const r = w.current, o = r && n.get(r.id);
92
+ if (!r || !m.current || !o) return;
93
+ const l = m.current.getBoundingClientRect();
94
+ o.style.transform = `translate(${r.x - r.dx - l.left}px, ${r.y - r.dy - l.top}px)`;
95
+ };
96
+ de(() => {
97
+ const r = /* @__PURE__ */ new Map(), o = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
98
+ for (const [l, g] of n) {
99
+ const M = h.current.get(l);
100
+ r.set(l, [g.offsetLeft, g.offsetTop]), y && !o && M && l !== y.id && (M[0] !== g.offsetLeft || M[1] !== g.offsetTop) && g.animate([{ transform: `translate(${M[0] - g.offsetLeft}px, ${M[1] - g.offsetTop}px)` }, { transform: "none" }], { duration: 150, easing: "ease-out" });
101
+ }
102
+ h.current = r, y && T();
103
+ });
104
+ const k = () => {
105
+ const r = w.current, o = s.current, l = r && n.get(r.id);
106
+ if (!r || !o || !l) return;
107
+ const g = getComputedStyle(o), M = parseFloat(g.columnGap), F = g.gridTemplateColumns.split(" ").length, O = o.getBoundingClientRect(), R = d[r.from].span, Y = { cell: (O.width - (F - 1) * M) / F, gap: M, unit: (l.offsetHeight - (R.rows - 1) * M) / R.rows }, I = at(d.map((V) => V.span), F, r.from, r.x - r.dx - O.left, r.y - r.dy - O.top, Y);
108
+ if (I === r.target) return T();
109
+ r.target = I, t({ id: r.id, target: I });
110
+ }, W = (r) => {
111
+ const o = w.current;
112
+ if (w.current = null, window.clearInterval(i.current.timer), i.current.escape && document.removeEventListener("keydown", i.current.escape), o && n.get(o.id)?.style.removeProperty("transform"), t(null), !!o?.started) {
113
+ if (!r) return C(d[o.from].title);
114
+ c(o.id, d[o.target].id);
115
+ }
116
+ };
117
+ de(() => {
118
+ p.current = { update: k, finish: W };
119
+ });
120
+ const U = (r, o) => (l) => {
121
+ const g = l.target;
122
+ if (l.button > 0 || g.closest("button") || !l.currentTarget.contains(g)) return;
123
+ const M = n.get(r).getBoundingClientRect();
124
+ w.current = { id: r, from: o, x0: l.clientX, y0: l.clientY, x: l.clientX, y: l.clientY, dx: l.clientX - M.left, dy: l.clientY - M.top, started: !1, target: o }, l.currentTarget.setPointerCapture(l.pointerId);
125
+ }, A = (r) => {
126
+ const o = w.current;
127
+ if (o) {
128
+ if (o.x = r.clientX, o.y = r.clientY, !o.started) {
129
+ if (Math.hypot(o.x - o.x0, o.y - o.y0) <= 4) return;
130
+ o.started = !0, t({ id: o.id, target: o.from }), i.current.escape = (l) => l.key === "Escape" && p.current.finish(!1), document.addEventListener("keydown", i.current.escape), i.current.timer = window.setInterval(() => {
131
+ const g = w.current?.y ?? window.innerHeight / 2, M = Math.ceil((64 - Math.min(g, window.innerHeight - g)) / 4);
132
+ M <= 0 || (window.scrollBy(0, g < window.innerHeight / 2 ? -M : M), p.current.update());
133
+ }, 16);
134
+ }
135
+ p.current.update();
136
+ }
137
+ };
138
+ return {
139
+ drag: y,
140
+ /** To go on the placeholder. */
141
+ placeholder: m,
142
+ /** The pointer handlers of a widget's head. */
143
+ handlers: (r, o) => ({
144
+ onPointerDown: U(r, o),
145
+ onPointerMove: A,
146
+ onPointerUp: () => p.current.finish(!0),
147
+ onPointerCancel: () => p.current.finish(!1)
148
+ })
149
+ };
150
+ }
151
+ function be(s) {
152
+ return null;
153
+ }
154
+ function lt() {
155
+ return null;
156
+ }
157
+ const ut = "heading", q = (s, n) => s.title?.(n) ?? s.label;
158
+ function ht({ options: s, change: n }) {
159
+ const d = ae();
160
+ return /* @__PURE__ */ a(ve, { label: d.dashboardHeadingField, children: /* @__PURE__ */ a(Ce, { value: s.text, onChange: (c) => n({ text: c.target.value }) }) });
161
+ }
162
+ function ft(s) {
163
+ return {
164
+ name: ut,
165
+ label: s.dashboardHeadingLabel,
166
+ sentence: s.dashboardHeadingSentence,
167
+ sizes: { full: { columns: 12, rows: 1 } },
168
+ start: "full",
169
+ defaultOptions: { text: s.dashboardHeadingText },
170
+ title: (n) => n.text || s.dashboardHeadingLabel,
171
+ form: ht,
172
+ children: () => null
173
+ };
174
+ }
175
+ const Q = (...s) => s.filter(Boolean).join(" "), ue = 12;
176
+ function pt({ render: s, ...n }) {
177
+ return s(n);
178
+ }
179
+ const he = (s, n) => ({ "--column": s.column + 1, "--row": s.row + 1, "--columns": s.columns, "--rows": s.rows, "--order": n }), fe = (s) => s.columns * s.rows, ee = (s, n) => (d) => {
180
+ d ? s.set(n, d) : s.delete(n);
181
+ };
182
+ function gt({ view: s, editor: n, className: d, children: c, catalogue: C, ...y }) {
183
+ const t = ae(), w = oe(() => ft(t), [t]), i = /* @__PURE__ */ new Map();
184
+ for (const e of ze.toArray(c))
185
+ ie(e) && e.type === be ? i.set(e.props.name, e.props) : ie(e) && e.type === lt && i.set(w.name, w);
186
+ const { register: h } = n;
187
+ ne(() => h(i));
188
+ const m = ge(s, i), p = m.widgets.flatMap((e) => {
189
+ const u = i.get(e.type), x = u?.sizes[e.size];
190
+ return u && x ? [{ widget: e, type: u, span: x }] : [];
191
+ }), T = E(null), [k] = Z(() => /* @__PURE__ */ new Map()), [W] = Z(() => /* @__PURE__ */ new Map()), [U] = Z(() => /* @__PURE__ */ new Map()), A = E(null), { editing: r } = n, o = m.widgets.length, l = (e, u, x) => {
192
+ const S = m.widgets.findIndex((D) => D.id === e), v = Math.max(0, Math.min(o - 1, x));
193
+ S !== v && (n.step(Re(e, v)), $(t.dashboardMoved(u, S + 1, v + 1, o)));
194
+ }, { drag: g, placeholder: M, handlers: F } = ct({
195
+ grid: T,
196
+ cards: k,
197
+ shown: p.map(({ widget: e, type: u, span: x }) => ({ id: e.id, span: x, title: q(u, e.options) })),
198
+ drop: (e, u) => {
199
+ const x = p.find((S) => S.widget.id === e);
200
+ l(e, q(x.type, x.widget.options), m.widgets.findIndex((S) => S.id === u));
201
+ },
202
+ cancelled: (e) => $(t.dashboardDragCancelled(e))
203
+ }), O = g ? p.findIndex((e) => e.widget.id === g.id) : -1, R = p.map((e, u) => u);
204
+ O >= 0 && R.splice(g.target, 0, ...R.splice(O, 1));
205
+ const Y = me(R.map((e) => p[e].span), ue), I = p.map((e, u) => Y[R.indexOf(u)]), [V, b] = Z(null), f = E(null), z = E(null);
206
+ ne(() => {
207
+ z.current && (k.get(z.current)?.scrollIntoView?.({ block: "nearest" }), z.current = null);
208
+ });
209
+ const N = E(!1);
210
+ ne(() => {
211
+ N.current !== !1 && ((N.current === null ? A.current : W.get(N.current))?.focus(), N.current = !1);
212
+ });
213
+ const P = [...i.values()].filter((e) => !C || C.includes(e.name)), G = (e) => {
214
+ const u = { id: Ie(m, e.name), type: e.name, size: e.start, options: e.defaultOptions };
215
+ z.current = u.id, n.step(Le(u)), $(t.dashboardAdded(q(e, u.options), o + 1, o + 1));
216
+ }, X = V ? i.get(m.widgets.find((e) => e.id === V)?.type ?? "") : void 0, te = m.widgets.find((e) => e.id === V), we = (e, u, x, S) => r ? {
217
+ ref: ee(W, e.id),
218
+ className: Q(_.head, _.editingHead),
219
+ ...F(e.id, x),
220
+ role: "group",
221
+ tabIndex: 0,
222
+ "aria-label": t.dashboardHead(u, S + 1, o),
223
+ onKeyDown: (v) => {
224
+ if (v.target !== v.currentTarget) return;
225
+ const D = { ArrowLeft: S - 1, ArrowUp: S - 1, ArrowRight: S + 1, ArrowDown: S + 1, Home: 0, End: o - 1 }[v.key];
226
+ D !== void 0 && (v.preventDefault(), l(e.id, u, D));
227
+ }
228
+ } : { className: _.head }, xe = (e, u, x, S, v) => {
229
+ const D = Object.entries(u.sizes).sort(([, j], [, J]) => fe(j) - fe(J));
230
+ return /* @__PURE__ */ H(
231
+ Oe,
232
+ {
233
+ align: "end",
234
+ trigger: /* @__PURE__ */ a(L, { ref: ee(U, e.id), size: "sm", variant: "ghost", "aria-label": t.dashboardMenu(x), children: /* @__PURE__ */ a("svg", { viewBox: "0 0 10 10", width: "10", height: "10", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", children: /* @__PURE__ */ a("path", { d: "M1.5 5h.01M5 5h.01M8.5 5h.01" }) }) }),
235
+ children: [
236
+ /* @__PURE__ */ a(B, { disabled: !0, children: t.dashboardPlace(v + 1, o) }),
237
+ /* @__PURE__ */ a(B, { disabled: v === 0, onSelect: () => l(e.id, x, 0), children: t.dashboardMoveStart }),
238
+ /* @__PURE__ */ a(B, { disabled: v === 0, onSelect: () => l(e.id, x, v - 1), children: t.dashboardMoveEarlier }),
239
+ /* @__PURE__ */ a(B, { disabled: v === o - 1, onSelect: () => l(e.id, x, v + 1), children: t.dashboardMoveLater }),
240
+ /* @__PURE__ */ a(B, { disabled: v === o - 1, onSelect: () => l(e.id, x, o - 1), children: t.dashboardMoveEnd }),
241
+ D.length > 1 && /* @__PURE__ */ H(re, { children: [
242
+ /* @__PURE__ */ a(se, {}),
243
+ D.map(([j, J]) => /* @__PURE__ */ a(
244
+ B,
245
+ {
246
+ "aria-current": j === e.size || void 0,
247
+ onSelect: () => {
248
+ n.step(Te(e.id, j)), $(t.dashboardResized(x, t.dashboardSizes[j]));
249
+ },
250
+ children: t.dashboardSizeEntry(t.dashboardSizes[j], it(J), j === e.size)
251
+ },
252
+ j
253
+ ))
254
+ ] }),
255
+ u.form && /* @__PURE__ */ H(re, { children: [
256
+ /* @__PURE__ */ a(se, {}),
257
+ /* @__PURE__ */ a(
258
+ B,
259
+ {
260
+ onSelect: () => {
261
+ f.current = U.get(e.id) ?? null, b(e.id);
262
+ },
263
+ children: t.dashboardOptions
264
+ }
265
+ )
266
+ ] }),
267
+ /* @__PURE__ */ a(se, {}),
268
+ /* @__PURE__ */ a(
269
+ B,
270
+ {
271
+ tone: "danger",
272
+ onSelect: () => {
273
+ N.current = (p[S + 1] ?? p[S - 1])?.widget.id ?? null, n.step(De(e.id)), $(t.dashboardRemoved(x));
274
+ },
275
+ children: t.dashboardRemove
276
+ }
277
+ )
278
+ ]
279
+ }
280
+ );
281
+ };
282
+ return /* @__PURE__ */ H("div", { role: "group", className: Q(_.root, d), "data-editing": r || void 0, ...y, children: [
283
+ n.allowed && /* @__PURE__ */ a("div", { className: _.bar, children: r ? /* @__PURE__ */ H(re, { children: [
284
+ /* @__PURE__ */ a("span", { className: _.mode, children: t.dashboardEditing }),
285
+ /* @__PURE__ */ a(L, { size: "sm", disabled: !n.canUndo, onClick: n.undo, children: t.dashboardUndo }),
286
+ /* @__PURE__ */ a(L, { size: "sm", disabled: !n.canRedo, onClick: n.redo, children: t.dashboardRedo }),
287
+ /* @__PURE__ */ a(L, { size: "sm", disabled: !n.canReset, onClick: n.reset, children: t.dashboardReset }),
288
+ /* @__PURE__ */ a(L, { ref: A, size: "sm", onClick: () => n.setCatalogue(!0), children: t.dashboardAddWidget }),
289
+ /* @__PURE__ */ a(L, { size: "sm", onClick: n.cancel, children: t.dashboardCancel }),
290
+ /* @__PURE__ */ a(L, { size: "sm", variant: "primary", onClick: n.done, children: t.dashboardDone })
291
+ ] }) : /* @__PURE__ */ a(L, { size: "sm", onClick: n.edit, children: t.dashboardEdit }) }),
292
+ r && o === 0 && /* @__PURE__ */ a(K, { children: t.dashboardEmptyEditing }),
293
+ !r && o === 0 && /* @__PURE__ */ a(K, { children: t.dashboardEmpty }),
294
+ /* @__PURE__ */ H("div", { className: _.grid, ref: T, children: [
295
+ p.map(({ widget: e, type: u }, x) => {
296
+ const S = he(I[x], R.indexOf(x)), v = q(u, e.options), D = m.widgets.findIndex((_e) => _e.id === e.id), j = we(e, v, x, D), J = r && xe(e, u, v, x, D);
297
+ return u === w ? /* @__PURE__ */ a("div", { ref: ee(k, e.id), className: Q(_.item, _.headingRow), style: S, "data-widget": e.id, "data-editing": r || void 0, "data-dragging": g?.id === e.id || void 0, children: /* @__PURE__ */ H("div", { ...j, className: Q(j.className, _.headingHead), children: [
298
+ r && /* @__PURE__ */ a(le, { className: _.grip }),
299
+ /* @__PURE__ */ a(Se, { level: 2, size: "lg", className: _.heading, children: e.options.text }),
300
+ J
301
+ ] }) }, e.id) : /* @__PURE__ */ H(Ne, { ref: ee(k, e.id), className: _.item, style: S, "data-widget": e.id, "data-editing": r || void 0, "data-dragging": g?.id === e.id || void 0, "aria-label": v, children: [
302
+ /* @__PURE__ */ H("div", { ...j, children: [
303
+ r && /* @__PURE__ */ a(le, { className: _.grip }),
304
+ /* @__PURE__ */ a("h2", { className: _.title, children: v }),
305
+ r && /* @__PURE__ */ a("span", { className: _.size, children: t.dashboardSizes[e.size] }),
306
+ J
307
+ ] }),
308
+ /* @__PURE__ */ a("div", { className: _.body, inert: r, children: /* @__PURE__ */ a(ot, { size: e.size, options: e.options, fallback: /* @__PURE__ */ a(K, { tone: "secondary", children: t.dashboardFailed }), children: /* @__PURE__ */ a(pt, { render: u.children, size: e.size, options: e.options }) }) })
309
+ ] }, e.id);
310
+ }),
311
+ O >= 0 && /* @__PURE__ */ a("div", { ref: M, "aria-hidden": "true", "data-placeholder": !0, className: Q(_.item, _.placeholder), style: he(I[O], g.target) })
312
+ ] }),
313
+ /* @__PURE__ */ H(ke, { open: n.catalogue, onClose: () => n.setCatalogue(!1), children: [
314
+ /* @__PURE__ */ a(je, { title: t.dashboardCatalogue, description: t.dashboardCatalogueSentence }),
315
+ /* @__PURE__ */ a(He, { children: /* @__PURE__ */ a("ul", { className: _.catalogue, children: P.map((e) => {
316
+ const u = e.sizes[e.start];
317
+ return /* @__PURE__ */ H("li", { className: _.entry, children: [
318
+ /* @__PURE__ */ a("div", { className: _.footprint, role: "img", "aria-label": t.dashboardFootprint(u.columns, u.rows), children: /* @__PURE__ */ a("span", { style: { width: `${u.columns / ue * 100}%`, height: `${Math.min(1, u.rows / 4) * 100}%` } }) }),
319
+ /* @__PURE__ */ H("div", { className: _.entryText, children: [
320
+ /* @__PURE__ */ a(K, { as: "strong", weight: "semibold", children: e.label }),
321
+ /* @__PURE__ */ a(K, { as: "span", size: "sm", tone: "secondary", children: e.sentence })
322
+ ] }),
323
+ /* @__PURE__ */ a(L, { size: "sm", "aria-label": t.dashboardAddType(e.label), onClick: () => G(e), children: t.dashboardAdd })
324
+ ] }, e.name);
325
+ }) }) })
326
+ ] }),
327
+ X?.form && te && /* @__PURE__ */ a(Ee, { open: !0, anchorRef: f, align: "end", onOpenChange: () => b(null), role: "dialog", ariaLabel: t.dashboardOptions, children: /* @__PURE__ */ a("div", { className: _.options, children: X.form({
328
+ options: te.options,
329
+ change: (e) => {
330
+ n.step($e(te.id, e)), $(t.dashboardOptionsChanged(q(X, e)));
331
+ }
332
+ }) }) })
333
+ ] });
334
+ }
335
+ const pe = (s, n) => JSON.stringify(s) === JSON.stringify(n);
336
+ function xt({ defaultView: s, initialView: n, allowEdit: d = !0, onViewChange: c, onDraftChange: C }) {
337
+ const y = E(null);
338
+ y.current ??= { state: { view: n ?? s, draft: null, known: 0 }, listeners: /* @__PURE__ */ new Set(), types: /* @__PURE__ */ new Map(), signature: "" };
339
+ const t = y.current, w = ae(), i = E({ defaultView: s, allowEdit: d, onViewChange: c, onDraftChange: C, wording: w });
340
+ i.current = { defaultView: s, allowEdit: d, onViewChange: c, onDraftChange: C, wording: w };
341
+ const h = oe(() => {
342
+ const U = (b) => (t.listeners.add(b), () => void t.listeners.delete(b)), A = () => t.state, r = (b) => {
343
+ t.state = b, t.listeners.forEach((f) => f());
344
+ }, o = (b) => t.types.size ? ge(b, t.types) : b, l = () => {
345
+ !i.current.allowEdit || t.state.draft || (r({ ...t.state, draft: { steps: [o(t.state.view)], at: 0 } }), i.current.onDraftChange?.(!0));
346
+ }, g = (b) => {
347
+ const { view: f, draft: z } = t.state;
348
+ if (!z) return;
349
+ const N = z.steps[z.at], P = b && !pe(N, o(f));
350
+ r({ ...t.state, view: P ? N : f, draft: null, catalogue: !1 }), i.current.onDraftChange?.(!1);
351
+ const { wording: G } = i.current;
352
+ $(b ? P ? G.dashboardKept : G.dashboardLeft : G.dashboardCancelled), P && i.current.onViewChange?.(N);
353
+ }, M = (b) => {
354
+ t.state.draft && r({ ...t.state, catalogue: b });
355
+ }, F = (b) => {
356
+ const { draft: f } = t.state;
357
+ if (!f) return;
358
+ const z = f.steps[f.at], N = b(z);
359
+ N !== z && r({ ...t.state, draft: { steps: [...f.steps.slice(0, f.at + 1), N], at: f.at + 1 } });
360
+ }, O = (b, f) => {
361
+ const { draft: z } = t.state;
362
+ z?.steps[z.at + b] && (r({ ...t.state, draft: { ...z, at: z.at + b } }), $(f));
363
+ }, R = () => O(-1, i.current.wording.dashboardUndone), Y = () => O(1, i.current.wording.dashboardRedone), I = () => {
364
+ t.state.draft && (F(() => o(i.current.defaultView)), $(i.current.wording.dashboardResetDone));
365
+ }, V = (b, f, z) => ({
366
+ allowed: b,
367
+ editing: f !== null,
368
+ edit: l,
369
+ done: () => g(!0),
370
+ cancel: () => g(!1),
371
+ catalogue: z,
372
+ setCatalogue: M,
373
+ register: (N) => {
374
+ t.types = N;
375
+ const P = [...N].map(([G, X]) => `${G}:${X.start}:${JSON.stringify(X.sizes)}`).join("|");
376
+ P !== t.signature && (t.signature = P, r({ ...t.state, known: t.state.known + 1 }));
377
+ },
378
+ step: F,
379
+ undo: R,
380
+ redo: Y,
381
+ reset: I,
382
+ canUndo: !!f && f.at > 0,
383
+ canRedo: !!f && f.at < f.steps.length - 1,
384
+ canReset: !!f && !pe(f.steps[f.at], o(i.current.defaultView))
385
+ });
386
+ return {
387
+ get: A,
388
+ subscribe: U,
389
+ edit: l,
390
+ done: () => g(!0),
391
+ cancel: () => g(!1),
392
+ undo: R,
393
+ redo: Y,
394
+ reset: I,
395
+ catalogue: () => M(!0),
396
+ read: o,
397
+ setView: (b) => {
398
+ const f = o(b);
399
+ r({ ...t.state, view: f, draft: t.state.draft ? { steps: [f], at: 0 } : null });
400
+ },
401
+ Dashboard: (b) => {
402
+ const { view: f, draft: z, catalogue: N } = ce(U, A, A);
403
+ return Me(gt, { ...b, view: z ? z.steps[z.at] : f, editor: V(i.current.allowEdit, z, !!N) });
404
+ }
405
+ };
406
+ }, [t]), { view: m, draft: p, known: T } = ce(h.subscribe, h.get, h.get), k = p ? p.steps[p.at] : m, W = oe(() => h.read(k), [h, k, T]);
407
+ return {
408
+ Dashboard: h.Dashboard,
409
+ WidgetType: be,
410
+ view: W,
411
+ setView: h.setView,
412
+ editing: p !== null,
413
+ edit: h.edit,
414
+ done: h.done,
415
+ cancel: h.cancel,
416
+ catalogue: h.catalogue,
417
+ undo: h.undo,
418
+ redo: h.redo,
419
+ reset: h.reset
420
+ };
421
+ }
422
+ export {
423
+ lt as HeadingType,
424
+ be as WidgetType,
425
+ xt as useDashboard
426
+ };