@c2n/dashboard 0.0.11

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Nguyen Thai Vinh
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 THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # @c2n/dashboard
2
+
3
+ A grid of resizable panes: `c2-dashboard` owns the tracks, `c2-dash-card` owns one pane of them.
4
+
5
+ A pane draws a handle on every edge it shares with a neighbour. Dragging one resizes the **track**, not the pane, so
6
+ everything on either side of it stays aligned; the first drag switches that track to pixels and the `fr` tracks
7
+ around it give up the space in proportion to their weight, down to the minimums. A handle is a window splitter: it
8
+ is in the tab order, and the arrow keys move it 10px a step, 1px with Shift.
9
+
10
+ A pane has no surface of its own — put a `c2-card`, or any markup, in it.
11
+
12
+ ```bash
13
+ npm install @c2n/dashboard
14
+ ```
15
+
16
+ ## Markup
17
+
18
+ ```html
19
+ <script type="module">
20
+ import '@c2n/dashboard'
21
+ </script>
22
+
23
+ <c2-dashboard columns="320px 1fr" rows="1fr 1fr" storage-key="trading-desk" style="height: 480px">
24
+ <c2-dash-card col="1" row="1" row-span="2" min-width="240">
25
+ <span slot="header">Watchlist</span>
26
+ <c2-card>…</c2-card>
27
+ </c2-dash-card>
28
+
29
+ <c2-dash-card col="2" row="1" min-height="120" expand-full>
30
+ <span slot="header">Price</span>
31
+ <c2-line-chart></c2-line-chart>
32
+ </c2-dash-card>
33
+
34
+ <c2-dash-card col="2" row="2">
35
+ <span slot="header">Orders</span>
36
+ <c2-table></c2-table>
37
+ </c2-dash-card>
38
+ </c2-dashboard>
39
+ ```
40
+
41
+ `columns` and `rows` take a track count (`columns="3"`) or an explicit list (`columns="320px 1fr"`). The grid needs a
42
+ height whenever its rows use `fr`, since the row tracks divide the host's height.
43
+
44
+ ## Layout
45
+
46
+ ```js
47
+ const grid = document.querySelector('c2-dashboard')
48
+
49
+ // The sizes at the end of every gesture — also what `storage-key` writes to localStorage.
50
+ grid.addEventListener('layout-change', (event) => {
51
+ console.log(event.detail.columns, event.detail.rows)
52
+ })
53
+
54
+ // Placement chosen at runtime, keyed by each card's `card-id`; every field wins over the card's own attributes.
55
+ grid.layout = {
56
+ watchlist: { col: 1, row: 1, rowSpan: 2 },
57
+ orders: { visible: false },
58
+ }
59
+
60
+ grid.reset() // back to the authored tracks, and the stored ones are forgotten
61
+ ```
62
+
63
+ ## Expanding
64
+
65
+ `expand-width`, `expand-height` and `expand-full` add controls that grow a pane over the whole row, column or grid.
66
+ They move into the header row when the `header` or `actions` slot is filled, and float over the top-right corner
67
+ when neither is. The state is the `expanded` property (`none` | `width` | `height` | `full`), so an app can drive it
68
+ from its own button, and `expand-change` reports it.
69
+
70
+ The controls are not a closed set. `actions` puts your own buttons at the end of the header row, `controls` puts
71
+ them inside the built-in group immediately before the expand buttons, `footer` adds a status row under the content,
72
+ and every built-in icon is a slot whose fallback is the default drawing:
73
+
74
+ ```html
75
+ <c2-dash-card col="2" row="1" expand-full>
76
+ <span slot="header">Price</span>
77
+ <c2-badge slot="actions" tone="success">Live</c2-badge>
78
+ <c2-icon-button slot="controls" aria-label="Order settings">…</c2-icon-button>
79
+ <c2-feather-maximize slot="expand-full-icon"></c2-feather-maximize>
80
+ <c2-feather-minimize slot="collapse-full-icon"></c2-feather-minimize>
81
+ <c2-line-chart></c2-line-chart>
82
+ <span slot="footer">updated 14:32</span>
83
+ </c2-dash-card>
84
+ ```
85
+
86
+ An expanded pane covers its neighbours, so give it a background:
87
+
88
+ ```css
89
+ c2-dash-card {
90
+ --c2-dash-card--background: #ffffff;
91
+ --c2-dash-card__expanded--background: #ffffff;
92
+ }
93
+ ```
94
+
95
+ ## Theming
96
+
97
+ Everything visible is a CSS custom property — the gutter (`--c2-dashboard--gap`), the pane surface
98
+ (`--c2-dash-card--background`, `--c2-dash-card--border`, `--c2-dash-card--border-radius`), the header row
99
+ (`--c2-dash-card__header--*`), the expand controls (`--c2-dash-card__control--*`) and the handles
100
+ (`--c2-dash-card__handle--size`, `--c2-dash-card__handle__hover--background`). See the component page for the
101
+ complete table.
@@ -0,0 +1,353 @@
1
+ import { DASHBOARD_TAG } from "./dashboard-host.js";
2
+ import { LitElement, html, nothing, svg, unsafeCSS } from "lit";
3
+ import { property, state } from "lit/decorators.js";
4
+ import { isServer } from "lit-html/is-server.js";
5
+ import { customElement } from "@c2n/core/element-helper.js";
6
+ import { classMap } from "lit/directives/class-map.js";
7
+ //#region src/dash-card.scss?inline
8
+ var dash_card_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n position: relative;\n display: block;\n box-sizing: border-box;\n grid-column: var(--_grid-column, auto);\n grid-row: var(--_grid-row, auto);\n min-width: 0;\n min-height: 0;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n:host([expanded=width]),\n:host([expanded=height]),\n:host([expanded=full]) {\n isolation: isolate;\n z-index: var(--c2-dash-card__expanded--z-index,20);\n background: var(--c2-dash-card__expanded--background,var(--c2-dash-card--background,transparent));\n animation: c2-dash-card-expand var(--c2-dash-card__expanded--animation-duration,200ms) cubic-bezier(0.25, 0.46, 0.45, 0.94);\n}\n\n@keyframes c2-dash-card-expand {\n from {\n transform: scale(0.97);\n }\n to {\n transform: scale(1);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n :host([expanded=width]),\n :host([expanded=height]),\n :host([expanded=full]) {\n animation: none;\n }\n}\n.c2-dash-card {\n position: relative;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n height: 100%;\n min-width: 0;\n min-height: 0;\n padding: var(--c2-dash-card--padding,0px);\n overflow: var(--c2-dash-card--overflow,hidden);\n color: inherit;\n background: var(--c2-dash-card--background,transparent);\n border: var(--c2-dash-card--border,none);\n border-radius: var(--c2-dash-card--border-radius,0px);\n box-shadow: var(--c2-dash-card--box-shadow,none);\n}\n\n.c2-dash-card__header {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n justify-content: space-between;\n box-sizing: border-box;\n gap: var(--c2-dash-card__header--gap,12px);\n min-width: 0;\n min-height: var(--c2-dash-card__header--min-height,44px);\n padding: var(--c2-dash-card__header--padding-block,8px) var(--c2-dash-card__header--padding-inline,12px);\n font-size: var(--c2-dash-card__header--font-size,14px);\n font-weight: var(--c2-dash-card__header--font-weight,600);\n color: var(--c2-dash-card__header--color,#18181b);\n background: var(--c2-dash-card__header--background,transparent);\n border-bottom: var(--c2-dash-card__header--border-bottom,none);\n}\n\n.c2-dash-card:not(.has-header) .c2-dash-card__header {\n position: absolute;\n top: var(--c2-dash-card__controls--offset,4px);\n right: var(--c2-dash-card__controls--offset,4px);\n z-index: 2;\n min-height: 0;\n padding: 0;\n background: none;\n border: none;\n}\n\n.c2-dash-card:not(.has-header) .c2-dash-card__title {\n display: none;\n}\n\n.c2-dash-card__title {\n display: flex;\n flex: 1 1 auto;\n align-items: center;\n min-width: 0;\n}\n\n.c2-dash-card__actions {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: var(--c2-dash-card__actions--gap,8px);\n min-width: 0;\n}\n\n.c2-dash-card__controls {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: var(--c2-dash-card__controls--gap,2px);\n}\n\n.c2-dash-card__control {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--c2-dash-card__control--size,24px);\n height: var(--c2-dash-card__control--size,24px);\n padding: 0;\n margin: 0;\n color: var(--c2-dash-card__control--color,#71717a);\n cursor: pointer;\n background: var(--c2-dash-card__control--background,transparent);\n border: none;\n border-radius: var(--c2-dash-card__control--border-radius,6px);\n}\n\n.c2-dash-card__control:hover {\n color: var(--c2-dash-card__control__hover--color,#18181b);\n background: var(--c2-dash-card__control__hover--background,#f4f4f5);\n}\n\n.c2-dash-card__control:focus-visible {\n outline: var(--c2-dash-card__control__focus--outline,2px solid rgba(2, 101, 220, 0.4));\n outline-offset: var(--c2-dash-card__control__focus--outline-offset,2px);\n}\n\n.c2-dash-card__control-icon {\n width: var(--c2-dash-card__control--icon-size,16px);\n height: var(--c2-dash-card__control--icon-size,16px);\n}\n\n.c2-dash-card__control ::slotted(*) {\n width: var(--c2-dash-card__control--icon-size,16px);\n height: var(--c2-dash-card__control--icon-size,16px);\n}\n\n.c2-dash-card__body {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n}\n\n.c2-dash-card__footer {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n box-sizing: border-box;\n gap: var(--c2-dash-card__footer--gap,8px);\n min-width: 0;\n min-height: var(--c2-dash-card__footer--min-height,36px);\n padding: var(--c2-dash-card__footer--padding-block,6px) var(--c2-dash-card__footer--padding-inline,12px);\n font-size: var(--c2-dash-card__footer--font-size,12px);\n color: var(--c2-dash-card__footer--color,#71717a);\n background: var(--c2-dash-card__footer--background,transparent);\n border-top: var(--c2-dash-card__footer--border-top,none);\n}\n\n.c2-dash-card__handle {\n position: absolute;\n z-index: 3;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--c2-dash-card__handle--background,transparent);\n touch-action: none;\n}\n\n.c2-dash-card__handle:focus-visible {\n outline: var(--c2-dash-card__handle__focus--outline,2px solid rgba(2, 101, 220, 0.4));\n}\n\n.c2-dash-card__handle-bar {\n display: block;\n background: transparent;\n border-radius: var(--c2-dash-card__handle--border-radius,999px);\n transition: background var(--c2-dash-card__handle--transition-duration,150ms) ease;\n}\n\n.c2-dash-card__handle:hover .c2-dash-card__handle-bar,\n.c2-dash-card__handle:focus-visible .c2-dash-card__handle-bar {\n background: var(--c2-dash-card__handle__hover--background,rgba(2, 101, 220, 0.2));\n}\n\n.c2-dash-card__handle.is-dragging .c2-dash-card__handle-bar {\n background: var(--c2-dash-card__handle__active--background,rgb(2, 101, 220));\n}\n\n.c2-dash-card__handle--left,\n.c2-dash-card__handle--right {\n top: 0;\n width: var(--c2-dash-card__handle--size,6px);\n height: 100%;\n cursor: col-resize;\n}\n.c2-dash-card__handle--left .c2-dash-card__handle-bar,\n.c2-dash-card__handle--right .c2-dash-card__handle-bar {\n width: 2px;\n height: calc(100% - 2 * var(--c2-dash-card__handle--inset,2px));\n}\n\n.c2-dash-card__handle--left {\n left: calc(-1 * var(--c2-dash-card__handle--size,6px) / 2);\n}\n\n.c2-dash-card__handle--right {\n right: calc(-1 * var(--c2-dash-card__handle--size,6px) / 2);\n}\n\n.c2-dash-card__handle--top,\n.c2-dash-card__handle--bottom {\n left: 0;\n width: 100%;\n height: var(--c2-dash-card__handle--size,6px);\n cursor: row-resize;\n}\n.c2-dash-card__handle--top .c2-dash-card__handle-bar,\n.c2-dash-card__handle--bottom .c2-dash-card__handle-bar {\n width: calc(100% - 2 * var(--c2-dash-card__handle--inset,2px));\n height: 2px;\n}\n\n.c2-dash-card__handle--top {\n top: calc(-1 * var(--c2-dash-card__handle--size,6px) / 2);\n}\n\n.c2-dash-card__handle--bottom {\n bottom: calc(-1 * var(--c2-dash-card__handle--size,6px) / 2);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .c2-dash-card__handle-bar {\n transition: none;\n }\n}";
9
+ //#endregion
10
+ //#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
11
+ function __decorate(decorators, target, key, desc) {
12
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
13
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
14
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
15
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
16
+ }
17
+ //#endregion
18
+ //#region src/dash-card.ts
19
+ var KEYBOARD_STEP = 10;
20
+ var KEYBOARD_FINE_STEP = 1;
21
+ var ICONS = {
22
+ expandWidth: svg`<polyline points="10 7 5 12 10 17" /><polyline points="14 7 19 12 14 17" />`,
23
+ collapseWidth: svg`<polyline points="5 7 10 12 5 17" /><polyline points="19 7 14 12 19 17" />`,
24
+ expandHeight: svg`<polyline points="7 10 12 5 17 10" /><polyline points="7 14 12 19 17 14" />`,
25
+ collapseHeight: svg`<polyline points="7 5 12 10 17 5" /><polyline points="7 19 12 14 17 19" />`,
26
+ expandFull: svg`<path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3" />`,
27
+ collapseFull: svg`<path d="M8 3v3a2 2 0 0 1-2 2H3M16 3v3a2 2 0 0 0 2 2h3M8 21v-3a2 2 0 0 0-2-2H3M16 21v-3a2 2 0 0 1 2-2h3" />`
28
+ };
29
+ function clamp(value, min, max) {
30
+ return Math.min(Math.max(value, min), max);
31
+ }
32
+ var DashCard = class DashCard extends LitElement {
33
+ constructor(..._args) {
34
+ super(..._args);
35
+ this.cardId = void 0;
36
+ this.col = 1;
37
+ this.row = 1;
38
+ this.colSpan = 1;
39
+ this.rowSpan = 1;
40
+ this.minWidth = 0;
41
+ this.minHeight = 0;
42
+ this.resize = "both";
43
+ this.expandWidth = false;
44
+ this.expandHeight = false;
45
+ this.expandFull = false;
46
+ this.expanded = "none";
47
+ this.hasHeader = false;
48
+ this.hasActions = false;
49
+ this.hasSlottedControls = false;
50
+ this.hasFooter = false;
51
+ this.dashboard = void 0;
52
+ }
53
+ static {
54
+ this.styles = unsafeCSS(dash_card_default);
55
+ }
56
+ /** The grid reads this during a drag; a hidden card asks for nothing. */
57
+ get constraint() {
58
+ if (this.hidden) return void 0;
59
+ const placement = this.placement;
60
+ return {
61
+ col: placement.col,
62
+ colSpan: placement.colSpan,
63
+ row: placement.row,
64
+ rowSpan: placement.rowSpan,
65
+ minWidth: Math.max(0, this.minWidth),
66
+ minHeight: Math.max(0, this.minHeight)
67
+ };
68
+ }
69
+ connectedCallback() {
70
+ super.connectedCallback();
71
+ this.attachToDashboard();
72
+ }
73
+ disconnectedCallback() {
74
+ this.dashboard?.unregisterCard(this);
75
+ this.dashboard = void 0;
76
+ super.disconnectedCallback();
77
+ }
78
+ /** Called by the grid when its shape or its layout record changed. */
79
+ hostChanged() {
80
+ this.requestUpdate();
81
+ }
82
+ willUpdate() {
83
+ const visible = this.dashboard?.placementOf(this.cardId)?.visible;
84
+ if (visible !== void 0) this.hidden = !visible;
85
+ }
86
+ updated() {
87
+ const placement = this.placement;
88
+ this.style.setProperty("--_grid-column", `${placement.col} / span ${placement.colSpan}`);
89
+ this.style.setProperty("--_grid-row", `${placement.row} / span ${placement.rowSpan}`);
90
+ this.syncHandleValues();
91
+ }
92
+ /**
93
+ * The splitter values are written straight onto the handles rather than re-rendered: a drag changes them on
94
+ * every pointer move, and nothing else about the card changes with them.
95
+ */
96
+ syncHandleValues() {
97
+ const dashboard = this.dashboard;
98
+ if (!dashboard) return;
99
+ for (const handle of this.renderRoot.querySelectorAll(".c2-dash-card__handle")) {
100
+ const axis = handle.dataset.axis === "column" ? "column" : "row";
101
+ const index = Number(handle.dataset.track);
102
+ if (!Number.isFinite(index)) continue;
103
+ handle.setAttribute("aria-valuenow", String(dashboard.splitterValue(axis, index)));
104
+ }
105
+ }
106
+ /** `slotchange` does not fire for a server-rendered shadow root, so read every slot once. */
107
+ firstUpdated() {
108
+ for (const slot of this.renderRoot.querySelectorAll("slot")) this.updateSection(slot);
109
+ }
110
+ /**
111
+ * The grid may upgrade after its cards do — the card element is in the document either way, so find it and wait
112
+ * for the definition rather than for a parent that is already alive.
113
+ */
114
+ attachToDashboard() {
115
+ const host = this.closest(DASHBOARD_TAG);
116
+ if (!host) return;
117
+ if (typeof host.registerCard !== "function") {
118
+ if (isServer) return;
119
+ customElements.whenDefined(DASHBOARD_TAG).then(() => {
120
+ if (this.isConnected) this.attachToDashboard();
121
+ });
122
+ return;
123
+ }
124
+ this.dashboard = host;
125
+ host.registerCard(this);
126
+ }
127
+ get placement() {
128
+ const override = this.dashboard?.placementOf(this.cardId);
129
+ const columnCount = this.dashboard?.columnCount ?? 0;
130
+ const rowCount = this.dashboard?.rowCount ?? 0;
131
+ const wide = this.expanded === "width" || this.expanded === "full";
132
+ const tall = this.expanded === "height" || this.expanded === "full";
133
+ let col = wide ? 1 : override?.col ?? this.col;
134
+ let row = tall ? 1 : override?.row ?? this.row;
135
+ let colSpan = wide ? Math.max(1, columnCount) : override?.colSpan ?? this.colSpan;
136
+ let rowSpan = tall ? Math.max(1, rowCount) : override?.rowSpan ?? this.rowSpan;
137
+ if (columnCount > 0) {
138
+ col = clamp(Math.trunc(col), 1, columnCount);
139
+ colSpan = clamp(Math.trunc(colSpan), 1, columnCount - col + 1);
140
+ }
141
+ if (rowCount > 0) {
142
+ row = clamp(Math.trunc(row), 1, rowCount);
143
+ rowSpan = clamp(Math.trunc(rowSpan), 1, rowCount - row + 1);
144
+ }
145
+ return {
146
+ col: Math.max(1, col),
147
+ row: Math.max(1, row),
148
+ colSpan: Math.max(1, colSpan),
149
+ rowSpan: Math.max(1, rowSpan)
150
+ };
151
+ }
152
+ handleSlotChange(event) {
153
+ this.updateSection(event.target);
154
+ }
155
+ updateSection(slot) {
156
+ const filled = slot.assignedNodes({ flatten: true }).some((node) => node.nodeType === Node.ELEMENT_NODE || (node.textContent ?? "").trim() !== "");
157
+ switch (slot.name) {
158
+ case "header":
159
+ this.hasHeader = filled;
160
+ break;
161
+ case "actions":
162
+ this.hasActions = filled;
163
+ break;
164
+ case "controls":
165
+ this.hasSlottedControls = filled;
166
+ break;
167
+ case "footer": this.hasFooter = filled;
168
+ }
169
+ }
170
+ startDrag(event, axis, index) {
171
+ const dashboard = this.dashboard;
172
+ if (!dashboard || event.button !== 0) return;
173
+ event.preventDefault();
174
+ const handle = event.currentTarget;
175
+ let last = axis === "column" ? event.clientX : event.clientY;
176
+ const move = (moveEvent) => {
177
+ const delta = (axis === "column" ? moveEvent.clientX : moveEvent.clientY) - last;
178
+ if (delta === 0) return;
179
+ last += axis === "column" ? dashboard.resizeColumn(index, delta) : dashboard.resizeRow(index, delta);
180
+ };
181
+ const finish = (endEvent) => {
182
+ handle.releasePointerCapture(endEvent.pointerId);
183
+ handle.removeEventListener("pointermove", move);
184
+ handle.removeEventListener("pointerup", finish);
185
+ handle.removeEventListener("pointercancel", finish);
186
+ handle.classList.remove("is-dragging");
187
+ dashboard.commitResize();
188
+ this.syncHandleValues();
189
+ };
190
+ handle.setPointerCapture(event.pointerId);
191
+ handle.classList.add("is-dragging");
192
+ handle.addEventListener("pointermove", move);
193
+ handle.addEventListener("pointerup", finish);
194
+ handle.addEventListener("pointercancel", finish);
195
+ }
196
+ handleHandleKeydown(event, axis, index) {
197
+ const dashboard = this.dashboard;
198
+ if (!dashboard) return;
199
+ const step = event.shiftKey ? KEYBOARD_FINE_STEP : KEYBOARD_STEP;
200
+ const back = axis === "column" ? "ArrowLeft" : "ArrowUp";
201
+ const forward = axis === "column" ? "ArrowRight" : "ArrowDown";
202
+ const delta = event.key === back ? -step : event.key === forward ? step : 0;
203
+ if (delta === 0) return;
204
+ event.preventDefault();
205
+ if (axis === "column") dashboard.resizeColumn(index, delta);
206
+ else dashboard.resizeRow(index, delta);
207
+ dashboard.commitResize();
208
+ this.syncHandleValues();
209
+ }
210
+ toggleExpanded(axis) {
211
+ let wide = this.expanded === "width" || this.expanded === "full";
212
+ let tall = this.expanded === "height" || this.expanded === "full";
213
+ if (axis === "width") wide = !wide;
214
+ else if (axis === "height") tall = !tall;
215
+ else wide = tall = !(wide && tall);
216
+ this.expanded = wide && tall ? "full" : wide ? "width" : tall ? "height" : "none";
217
+ this.dispatchEvent(new CustomEvent("expand-change", {
218
+ detail: { expanded: this.expanded },
219
+ bubbles: false,
220
+ composed: true
221
+ }));
222
+ }
223
+ renderControl(axis, active, label, icon) {
224
+ const slot = `${active ? "collapse" : "expand"}-${axis}-icon`;
225
+ return html`
226
+ <button class="c2-dash-card__control" part="control" type="button" aria-label=${label} aria-pressed=${active} @click=${() => this.toggleExpanded(axis)}>
227
+ <slot name=${slot}>
228
+ <svg
229
+ class="c2-dash-card__control-icon"
230
+ viewBox="0 0 24 24"
231
+ fill="none"
232
+ stroke="currentColor"
233
+ stroke-width="1.6"
234
+ stroke-linecap="round"
235
+ stroke-linejoin="round"
236
+ aria-hidden="true"
237
+ >
238
+ ${icon}
239
+ </svg>
240
+ </slot>
241
+ </button>
242
+ `;
243
+ }
244
+ renderControls() {
245
+ const wide = this.expanded === "width" || this.expanded === "full";
246
+ const tall = this.expanded === "height" || this.expanded === "full";
247
+ return html`
248
+ <slot name="controls" @slotchange=${this.handleSlotChange}></slot>
249
+ ${this.expandWidth ? this.renderControl("width", wide, wide ? "Collapse to the original width" : "Expand to the full width", wide ? ICONS.collapseWidth : ICONS.expandWidth) : nothing}
250
+ ${this.expandHeight ? this.renderControl("height", tall, tall ? "Collapse to the original height" : "Expand to the full height", tall ? ICONS.collapseHeight : ICONS.expandHeight) : nothing}
251
+ ${this.expandFull ? this.renderControl("full", wide && tall, wide && tall ? "Collapse the card" : "Expand the card", wide && tall ? ICONS.collapseFull : ICONS.expandFull) : nothing}
252
+ `;
253
+ }
254
+ renderHandle(axis, edge, index) {
255
+ return html`
256
+ <div
257
+ class="c2-dash-card__handle c2-dash-card__handle--${edge}"
258
+ part="handle handle-${edge}"
259
+ role="separator"
260
+ tabindex="0"
261
+ aria-orientation=${axis === "column" ? "vertical" : "horizontal"}
262
+ aria-label=${axis === "column" ? "Resize column" : "Resize row"}
263
+ aria-valuenow=${this.dashboard?.splitterValue(axis, index) ?? 0}
264
+ data-axis=${axis}
265
+ data-track=${index}
266
+ @pointerdown=${(event) => this.startDrag(event, axis, index)}
267
+ @keydown=${(event) => this.handleHandleKeydown(event, axis, index)}
268
+ >
269
+ <span class="c2-dash-card__handle-bar"></span>
270
+ </div>
271
+ `;
272
+ }
273
+ renderHandles() {
274
+ if (!this.dashboard || this.resize === "none") return nothing;
275
+ const placement = this.placement;
276
+ const horizontal = this.resize === "both" || this.resize === "horizontal";
277
+ const vertical = this.resize === "both" || this.resize === "vertical";
278
+ const wide = this.expanded === "width" || this.expanded === "full";
279
+ const tall = this.expanded === "height" || this.expanded === "full";
280
+ const lastColumn = placement.col + placement.colSpan - 1;
281
+ const lastRow = placement.row + placement.rowSpan - 1;
282
+ return html`
283
+ ${horizontal && !wide && lastColumn < this.dashboard.columnCount ? this.renderHandle("column", "right", lastColumn - 1) : nothing}
284
+ ${horizontal && !wide && placement.col > 1 ? this.renderHandle("column", "left", placement.col - 2) : nothing}
285
+ ${vertical && !tall && lastRow < this.dashboard.rowCount ? this.renderHandle("row", "bottom", lastRow - 1) : nothing}
286
+ ${vertical && !tall && placement.row > 1 ? this.renderHandle("row", "top", placement.row - 2) : nothing}
287
+ `;
288
+ }
289
+ render() {
290
+ const hasControls = this.hasSlottedControls || this.expandWidth || this.expandHeight || this.expandFull;
291
+ const hasHeaderRow = this.hasHeader || this.hasActions;
292
+ return html`
293
+ <div class=${classMap({
294
+ "c2-dash-card": true,
295
+ "has-header": hasHeaderRow
296
+ })}>
297
+ <div class="c2-dash-card__header" part="header" ?hidden=${!hasHeaderRow && !hasControls}>
298
+ <div class="c2-dash-card__title"><slot name="header" @slotchange=${this.handleSlotChange}></slot></div>
299
+ <div class="c2-dash-card__actions">
300
+ <slot name="actions" @slotchange=${this.handleSlotChange}></slot>
301
+ <div class="c2-dash-card__controls" part="controls" ?hidden=${!hasControls}>${this.renderControls()}</div>
302
+ </div>
303
+ </div>
304
+ <div class="c2-dash-card__body" part="body"><slot></slot></div>
305
+ <div class="c2-dash-card__footer" part="footer" ?hidden=${!this.hasFooter}>
306
+ <slot name="footer" @slotchange=${this.handleSlotChange}></slot>
307
+ </div>
308
+ </div>
309
+ ${this.renderHandles()}
310
+ `;
311
+ }
312
+ };
313
+ __decorate([property({ attribute: "card-id" })], DashCard.prototype, "cardId", void 0);
314
+ __decorate([property({ type: Number })], DashCard.prototype, "col", void 0);
315
+ __decorate([property({ type: Number })], DashCard.prototype, "row", void 0);
316
+ __decorate([property({
317
+ type: Number,
318
+ attribute: "col-span"
319
+ })], DashCard.prototype, "colSpan", void 0);
320
+ __decorate([property({
321
+ type: Number,
322
+ attribute: "row-span"
323
+ })], DashCard.prototype, "rowSpan", void 0);
324
+ __decorate([property({
325
+ type: Number,
326
+ attribute: "min-width"
327
+ })], DashCard.prototype, "minWidth", void 0);
328
+ __decorate([property({
329
+ type: Number,
330
+ attribute: "min-height"
331
+ })], DashCard.prototype, "minHeight", void 0);
332
+ __decorate([property({ reflect: true })], DashCard.prototype, "resize", void 0);
333
+ __decorate([property({
334
+ type: Boolean,
335
+ attribute: "expand-width"
336
+ })], DashCard.prototype, "expandWidth", void 0);
337
+ __decorate([property({
338
+ type: Boolean,
339
+ attribute: "expand-height"
340
+ })], DashCard.prototype, "expandHeight", void 0);
341
+ __decorate([property({
342
+ type: Boolean,
343
+ attribute: "expand-full"
344
+ })], DashCard.prototype, "expandFull", void 0);
345
+ __decorate([property({ reflect: true })], DashCard.prototype, "expanded", void 0);
346
+ __decorate([state()], DashCard.prototype, "hasHeader", void 0);
347
+ __decorate([state()], DashCard.prototype, "hasActions", void 0);
348
+ __decorate([state()], DashCard.prototype, "hasSlottedControls", void 0);
349
+ __decorate([state()], DashCard.prototype, "hasFooter", void 0);
350
+ __decorate([state()], DashCard.prototype, "dashboard", void 0);
351
+ DashCard = __decorate([customElement("c2-dash-card")], DashCard);
352
+ //#endregion
353
+ export { __decorate as n, DashCard as t };
@@ -0,0 +1,2 @@
1
+ import { t as DashCard } from "./dash-card-DYabhvcS.js";
2
+ export { DashCard };
@@ -0,0 +1,4 @@
1
+ //#region src/dashboard-host.ts
2
+ var DASHBOARD_TAG = "c2-dashboard";
3
+ //#endregion
4
+ export { DASHBOARD_TAG };
@@ -0,0 +1,244 @@
1
+ import { n as __decorate } from "./dash-card-DYabhvcS.js";
2
+ import { LitElement, html, unsafeCSS } from "lit";
3
+ import { property, state } from "lit/decorators.js";
4
+ import { isServer } from "lit-html/is-server.js";
5
+ import { customElement } from "@c2n/core/element-helper.js";
6
+ //#region src/dashboard.scss?inline
7
+ var dashboard_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: grid;\n box-sizing: border-box;\n grid-template-columns: var(--_columns, 1fr);\n grid-template-rows: var(--_rows, auto);\n gap: var(--c2-dashboard--gap,8px);\n padding: var(--c2-dashboard--padding,0px);\n background: var(--c2-dashboard--background,transparent);\n border: var(--c2-dashboard--border,none);\n border-radius: var(--c2-dashboard--border-radius,0px);\n min-width: 0;\n min-height: 0;\n}\n\n:host([hidden]) {\n display: none;\n}\n\nslot {\n display: contents;\n}\n\n::slotted(*) {\n min-width: 0;\n min-height: 0;\n}";
8
+ //#endregion
9
+ //#region src/dashboard.ts
10
+ var FRACTION = /^([\d.]*)fr$/;
11
+ /** The weight of an `fr` track, `0` for any track that does not flex. */
12
+ function parseFraction(size) {
13
+ const match = FRACTION.exec(size.trim());
14
+ if (!match) return 0;
15
+ if (match[1] === "") return 1;
16
+ const value = Number.parseFloat(match[1]);
17
+ return Number.isFinite(value) && value > 0 ? value : 0;
18
+ }
19
+ function repeatFraction(count) {
20
+ return Array.from({ length: Math.max(1, Math.trunc(count)) }, () => "1fr");
21
+ }
22
+ /** `3` and `'3'` are three `1fr` tracks; anything else is a list of simple track sizes, separated by spaces or commas. */
23
+ function toTracks(input) {
24
+ if (Array.isArray(input)) return input.length > 0 ? [...input] : ["1fr"];
25
+ if (typeof input === "number") return repeatFraction(input);
26
+ const text = String(input ?? "").trim();
27
+ if (text === "") return ["1fr"];
28
+ if (/^\d+$/.test(text)) return repeatFraction(Number.parseInt(text, 10));
29
+ const tracks = text.split(/[\s,]+/).filter(Boolean);
30
+ return tracks.length > 0 ? tracks : ["1fr"];
31
+ }
32
+ var Dashboard = class Dashboard extends LitElement {
33
+ constructor(..._args) {
34
+ super(..._args);
35
+ this.columns = 2;
36
+ this.rows = 1;
37
+ this.minColumnWidth = 50;
38
+ this.minRowHeight = 50;
39
+ this.storageKey = void 0;
40
+ this.layout = void 0;
41
+ this.columnTracks = [];
42
+ this.rowTracks = [];
43
+ this.cards = /* @__PURE__ */ new Set();
44
+ this.pixelCache = /* @__PURE__ */ new Map();
45
+ }
46
+ static {
47
+ this.styles = unsafeCSS(dashboard_default);
48
+ }
49
+ /** Column tracks as they are laid out right now. */
50
+ get columnSizes() {
51
+ return [...this.columnTracks];
52
+ }
53
+ /** Row tracks as they are laid out right now. */
54
+ get rowSizes() {
55
+ return [...this.rowTracks];
56
+ }
57
+ get columnCount() {
58
+ return this.columnTracks.length;
59
+ }
60
+ get rowCount() {
61
+ return this.rowTracks.length;
62
+ }
63
+ willUpdate(changed) {
64
+ if (!this.hasUpdated || changed.has("columns") || changed.has("rows") || changed.has("storageKey")) this.syncTracks();
65
+ }
66
+ updated(changed) {
67
+ this.pixelCache.clear();
68
+ this.style.setProperty("--_columns", this.columnTracks.join(" "));
69
+ this.style.setProperty("--_rows", this.rowTracks.join(" "));
70
+ if (changed.has("columnTracks") || changed.has("rowTracks") || changed.has("layout")) for (const card of this.cards) card.hostChanged();
71
+ }
72
+ /** Back to the authored tracks, and the stored ones are forgotten. */
73
+ reset() {
74
+ if (this.storageKey && !isServer) try {
75
+ localStorage.removeItem(this.storageKey);
76
+ } catch {}
77
+ this.columnTracks = toTracks(this.columns);
78
+ this.rowTracks = toTracks(this.rows);
79
+ this.emitLayoutChange();
80
+ }
81
+ registerCard(card) {
82
+ this.cards.add(card);
83
+ card.hostChanged();
84
+ }
85
+ unregisterCard(card) {
86
+ this.cards.delete(card);
87
+ }
88
+ placementOf(cardId) {
89
+ return cardId ? this.layout?.[cardId] : void 0;
90
+ }
91
+ resizeColumn(index, delta) {
92
+ this.pixelCache.clear();
93
+ const result = this.resizeTrack(this.columnTracks, index, delta, "column");
94
+ if (!result) return 0;
95
+ this.columnTracks = result.tracks;
96
+ return result.applied;
97
+ }
98
+ resizeRow(index, delta) {
99
+ this.pixelCache.clear();
100
+ const result = this.resizeTrack(this.rowTracks, index, delta, "row");
101
+ if (!result) return 0;
102
+ this.rowTracks = result.tracks;
103
+ return result.applied;
104
+ }
105
+ commitResize() {
106
+ this.persist();
107
+ this.emitLayoutChange();
108
+ }
109
+ /**
110
+ * Where the splitter after `index` sits, as a percentage of the tracks' total. A focusable `separator` is a
111
+ * window splitter, and a window splitter has to report a value.
112
+ */
113
+ splitterValue(axis, index) {
114
+ if (isServer || index < 0) return 0;
115
+ const sizes = this.trackPixels(axis);
116
+ const total = sizes.reduce((sum, size) => sum + size, 0);
117
+ if (total <= 0) return 0;
118
+ const leading = sizes.slice(0, index + 1).reduce((sum, size) => sum + size, 0);
119
+ return Math.round(leading / total * 100);
120
+ }
121
+ /**
122
+ * Reading a used track size forces layout, and a drag asks for it from every card's handles on every pointer
123
+ * move. The answer cannot change until something writes to the DOM, so it is cached until one of them does.
124
+ */
125
+ trackPixels(axis) {
126
+ const cached = this.pixelCache.get(axis);
127
+ if (cached) return cached;
128
+ const sizes = getComputedStyle(this)[axis === "column" ? "gridTemplateColumns" : "gridTemplateRows"].split(" ").map((size) => Number.parseFloat(size)).filter((size) => Number.isFinite(size));
129
+ this.pixelCache.set(axis, sizes);
130
+ return sizes;
131
+ }
132
+ /** What is left of the host along `axis` once the tracks, the gutters and the padding have taken their share. */
133
+ freeSpace(axis, tracks) {
134
+ const style = getComputedStyle(this);
135
+ const gap = Number.parseFloat(axis === "column" ? style.columnGap : style.rowGap) || 0;
136
+ const padding = axis === "column" ? (Number.parseFloat(style.paddingLeft) || 0) + (Number.parseFloat(style.paddingRight) || 0) : (Number.parseFloat(style.paddingTop) || 0) + (Number.parseFloat(style.paddingBottom) || 0);
137
+ const inner = (axis === "column" ? this.clientWidth : this.clientHeight) - padding;
138
+ const used = tracks.reduce((sum, size) => sum + size, 0) + gap * Math.max(0, tracks.length - 1);
139
+ return Math.max(0, inner - used);
140
+ }
141
+ emitLayoutChange() {
142
+ this.dispatchEvent(new CustomEvent("layout-change", {
143
+ detail: {
144
+ columns: [...this.columnTracks],
145
+ rows: [...this.rowTracks]
146
+ },
147
+ bubbles: false,
148
+ composed: true
149
+ }));
150
+ }
151
+ resizeTrack(tracks, index, delta, axis) {
152
+ if (isServer || index < 0 || index >= tracks.length) return void 0;
153
+ const minOf = (track) => this.trackMin(axis, track);
154
+ const computed = this.trackPixels(axis);
155
+ const current = computed[index];
156
+ if (!Number.isFinite(current)) return void 0;
157
+ let effective = delta;
158
+ if (delta > 0) {
159
+ let totalFraction = 0;
160
+ const flexible = [];
161
+ for (let track = 0; track < tracks.length; track++) {
162
+ if (track === index) continue;
163
+ const fraction = parseFraction(tracks[track]);
164
+ if (fraction <= 0) continue;
165
+ flexible.push({
166
+ px: computed[track] ?? 0,
167
+ min: minOf(track),
168
+ fraction
169
+ });
170
+ totalFraction += fraction;
171
+ }
172
+ for (const track of flexible) effective = Math.min(effective, Math.max(0, (track.px - track.min) * (totalFraction / track.fraction)));
173
+ if (flexible.length === 0) effective = Math.min(effective, this.freeSpace(axis, computed));
174
+ }
175
+ const next = Math.round(Math.max(minOf(index), current + effective));
176
+ const resized = [...tracks];
177
+ resized[index] = `${next}px`;
178
+ return {
179
+ tracks: resized,
180
+ applied: next - current
181
+ };
182
+ }
183
+ /** The largest minimum any card covering this track asks for, spread over the tracks that card spans. */
184
+ trackMin(axis, index) {
185
+ let min = axis === "column" ? this.minColumnWidth : this.minRowHeight;
186
+ for (const card of this.cards) {
187
+ const constraint = card.constraint;
188
+ if (!constraint) continue;
189
+ const start = (axis === "column" ? constraint.col : constraint.row) - 1;
190
+ const span = axis === "column" ? constraint.colSpan : constraint.rowSpan;
191
+ const size = axis === "column" ? constraint.minWidth : constraint.minHeight;
192
+ if (size <= 0 || index < start || index > start + span - 1) continue;
193
+ min = Math.max(min, size / span);
194
+ }
195
+ return Math.max(0, min);
196
+ }
197
+ syncTracks() {
198
+ const columns = toTracks(this.columns);
199
+ const rows = toTracks(this.rows);
200
+ const stored = this.readStored();
201
+ this.columnTracks = stored?.columns?.length === columns.length ? [...stored.columns] : columns;
202
+ this.rowTracks = stored?.rows?.length === rows.length ? [...stored.rows] : rows;
203
+ }
204
+ readStored() {
205
+ if (!this.storageKey || isServer) return null;
206
+ try {
207
+ const raw = localStorage.getItem(this.storageKey);
208
+ if (!raw) return null;
209
+ const parsed = JSON.parse(raw);
210
+ return parsed && typeof parsed === "object" ? parsed : null;
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+ persist() {
216
+ if (!this.storageKey || isServer) return;
217
+ try {
218
+ localStorage.setItem(this.storageKey, JSON.stringify({
219
+ columns: this.columnTracks,
220
+ rows: this.rowTracks
221
+ }));
222
+ } catch {}
223
+ }
224
+ render() {
225
+ return html`<slot></slot>`;
226
+ }
227
+ };
228
+ __decorate([property()], Dashboard.prototype, "columns", void 0);
229
+ __decorate([property()], Dashboard.prototype, "rows", void 0);
230
+ __decorate([property({
231
+ type: Number,
232
+ attribute: "min-column-width"
233
+ })], Dashboard.prototype, "minColumnWidth", void 0);
234
+ __decorate([property({
235
+ type: Number,
236
+ attribute: "min-row-height"
237
+ })], Dashboard.prototype, "minRowHeight", void 0);
238
+ __decorate([property({ attribute: "storage-key" })], Dashboard.prototype, "storageKey", void 0);
239
+ __decorate([property({ attribute: false })], Dashboard.prototype, "layout", void 0);
240
+ __decorate([state()], Dashboard.prototype, "columnTracks", void 0);
241
+ __decorate([state()], Dashboard.prototype, "rowTracks", void 0);
242
+ Dashboard = __decorate([customElement("c2-dashboard")], Dashboard);
243
+ //#endregion
244
+ export { Dashboard };
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@c2n/dashboard",
3
+ "version": "0.0.11",
4
+ "type": "module",
5
+ "main": "dist/dashboard.js",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./types/src/dashboard.d.ts",
9
+ "default": "./dist/dashboard.js"
10
+ },
11
+ "./dash-card.js": {
12
+ "types": "./types/src/dash-card.d.ts",
13
+ "default": "./dist/dash-card.js"
14
+ },
15
+ "./dashboard-host.js": {
16
+ "types": "./types/src/dashboard-host.d.ts",
17
+ "default": "./dist/dashboard-host.js"
18
+ },
19
+ "./react": {
20
+ "types": "./react.d.ts",
21
+ "default": "./react.js"
22
+ },
23
+ "./vue": {
24
+ "types": "./vue.d.ts",
25
+ "default": "./vue.js"
26
+ },
27
+ "./custom-elements.json": "./custom-elements.json"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "types",
32
+ "react.d.ts",
33
+ "react.js",
34
+ "vue.d.ts",
35
+ "vue.js"
36
+ ],
37
+ "keywords": [
38
+ "dashboard",
39
+ "grid",
40
+ "resizable",
41
+ "layout",
42
+ "web component",
43
+ "lit"
44
+ ],
45
+ "license": "MIT",
46
+ "author": "code2nguyen@gmail.com",
47
+ "publishConfig": {
48
+ "registry": "https://registry.npmjs.org",
49
+ "access": "public"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/code2nguyen/web-components.git"
54
+ },
55
+ "scripts": {
56
+ "dev": "vite",
57
+ "build": "wireit",
58
+ "build:only": "vite build",
59
+ "type-check": "wireit"
60
+ },
61
+ "wireit": {
62
+ "type-check": {
63
+ "dependencies": [
64
+ "../../core:build"
65
+ ],
66
+ "command": "tsc -p tsconfig.lib.json --composite false"
67
+ },
68
+ "build": {
69
+ "dependencies": [
70
+ "type-check"
71
+ ],
72
+ "command": "vite build"
73
+ }
74
+ },
75
+ "dependencies": {
76
+ "@c2n/core": "0.0.11",
77
+ "lit": "3.3.3"
78
+ },
79
+ "devDependencies": {
80
+ "@c2n/config": "*"
81
+ },
82
+ "customElements": "custom-elements.json",
83
+ "gitHead": "bf962fba5e49a0ac7555a082584e262949d323a5"
84
+ }
package/react.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ // GENERATED by @c2n/framework-types. Do not edit by hand.
2
+ //
3
+ // JSX types for the custom elements of @c2n/dashboard. Import once, anywhere in the program:
4
+ //
5
+ // import '@c2n/dashboard/react'
6
+ //
7
+ // Register the elements at module scope before React renders, or React writes an object prop as an
8
+ // attribute and it stringifies.
9
+
10
+ import type { DetailedHTMLProps, HTMLAttributes } from 'react'
11
+ import type { DashCard } from '@c2n/dashboard/dash-card.js'
12
+ import type { Dashboard } from '@c2n/dashboard'
13
+
14
+ /** Standard React host-element attributes plus the element's own public properties. */
15
+ type C2Props<T> = DetailedHTMLProps<HTMLAttributes<T>, T> & Partial<Omit<T, keyof HTMLElement>>
16
+
17
+ declare module 'react' {
18
+ namespace JSX {
19
+ interface IntrinsicElements {
20
+ 'c2-dash-card': C2Props<DashCard>
21
+ 'c2-dashboard': C2Props<Dashboard>
22
+ }
23
+ }
24
+ }
25
+
26
+ export {}
package/react.js ADDED
@@ -0,0 +1,3 @@
1
+ // GENERATED by @c2n/framework-types. Do not edit by hand.
2
+ // Types only — see the matching .d.ts.
3
+ export {}
@@ -0,0 +1,194 @@
1
+ import { LitElement, type TemplateResult } from 'lit';
2
+ import type { TypedAddEventListener, TypedRemoveEventListener } from '@c2n/core/event-helper.js';
3
+ import { type DashCardConstraint } from './dashboard-host';
4
+ /** Which edges of the card can be dragged. */
5
+ export type DashCardResize = 'both' | 'horizontal' | 'vertical' | 'none';
6
+ /** How far the card is expanded over its authored placement. */
7
+ export type DashCardExpanded = 'none' | 'width' | 'height' | 'full';
8
+ /** Detail of the `expand-change` event. */
9
+ export interface DashCardExpandChangeDetail {
10
+ /** The new expansion state. */
11
+ expanded: DashCardExpanded;
12
+ }
13
+ /** Events fired by {@link DashCard}, keyed for `addEventListener`. */
14
+ export interface DashCardEventMap {
15
+ 'expand-change': CustomEvent<DashCardExpandChangeDetail>;
16
+ }
17
+ export interface DashCard {
18
+ addEventListener: TypedAddEventListener<DashCard, DashCardEventMap>;
19
+ removeEventListener: TypedRemoveEventListener<DashCard, DashCardEventMap>;
20
+ }
21
+ /**
22
+ * One pane of a `c2-dashboard`. It owns its place in the grid (`col`, `row`, `col-span`, `row-span`), the drag
23
+ * handles on the edges it shares with a neighbour, and the optional expand controls; it draws no surface of its
24
+ * own, so put a `c2-card` — or any markup — in the default slot.
25
+ *
26
+ * A handle resizes the whole grid track, never just this card, and it only appears where there is a neighbour to
27
+ * take the space from: the right edge has one when another column follows, the left edge when one precedes. Drag
28
+ * it, or focus it and use the arrow keys (10px a step, 1px with Shift).
29
+ *
30
+ * Filling the `header`, `actions` or `footer` slot turns on that row; with the header and actions both empty the
31
+ * expand controls float over the top-right corner of the pane instead. An expanded card covers its neighbours, so
32
+ * give it — or the card inside it — a background.
33
+ *
34
+ * Every built-in icon is a slot whose fallback is the default drawing, so a pane can use the host application's own
35
+ * icon set without giving up the behaviour.
36
+ *
37
+ * @tag c2-dash-card
38
+ *
39
+ * @slot - Pane content. Give it `height: 100%` (a `c2-card` already stretches) so it follows the row track.
40
+ * @slot header - Title area of the header row; filling it turns the row on.
41
+ * @slot actions - Your own controls at the end of the header row, before the built-in expand buttons and separated
42
+ * from them by `--c2-dash-card__actions--gap`.
43
+ * @slot controls - Your own controls inside the built-in group, immediately before the expand buttons — for a
44
+ * button that belongs with them, such as a refresh or a close.
45
+ * @slot footer - Status bar under the pane content; filling it turns the row on.
46
+ * @slot expand-width-icon - Icon of the full-width control while the card is not expanded that way.
47
+ * @slot collapse-width-icon - Icon of the full-width control while it is.
48
+ * @slot expand-height-icon - Icon of the full-height control while the card is not expanded that way.
49
+ * @slot collapse-height-icon - Icon of the full-height control while it is.
50
+ * @slot expand-full-icon - Icon of the fullscreen control while the card is not expanded.
51
+ * @slot collapse-full-icon - Icon of the fullscreen control while it is.
52
+ *
53
+ * @event {CustomEvent<DashCardExpandChangeDetail>} expand-change - The card was expanded or collapsed through one
54
+ * of its own controls. Does not bubble: listen on the element.
55
+ *
56
+ * @csspart header - The header row, or the floating control group when the header and actions slots are empty.
57
+ * @csspart body - The wrapper around the default slot.
58
+ * @csspart footer - The footer row.
59
+ * @csspart controls - The group holding the `controls` slot and the built-in expand buttons.
60
+ * @csspart control - One built-in expand button.
61
+ * @csspart handle - Every drag handle. Each also carries the part of its edge.
62
+ * @csspart handle-left - The handle on the left edge.
63
+ * @csspart handle-right - The handle on the right edge.
64
+ * @csspart handle-top - The handle on the top edge.
65
+ * @csspart handle-bottom - The handle on the bottom edge.
66
+ *
67
+ * @cssproperty {background} [--c2-dash-card--background=transparent]
68
+ * @cssproperty {border} [--c2-dash-card--border=none]
69
+ * @cssproperty {border-radius} [--c2-dash-card--border-radius=0px]
70
+ * @cssproperty {box-shadow} [--c2-dash-card--box-shadow=none]
71
+ * @cssproperty {padding} [--c2-dash-card--padding=0px]
72
+ * @cssproperty {overflow} [--c2-dash-card--overflow=hidden] - `hidden` keeps the content inside the track; switch
73
+ * it to `visible` for a pane that has to overflow, such as one holding a menu.
74
+ *
75
+ * @cssproperty {background} --c2-dash-card__expanded--background - Fill while the card is expanded over its
76
+ * neighbours; falls back to `--c2-dash-card--background`, which is transparent, so set one of the two.
77
+ * @cssproperty {number} [--c2-dash-card__expanded--z-index=20]
78
+ * @cssproperty {duration} [--c2-dash-card__expanded--animation-duration=200ms] - Length of the scale-in the card
79
+ * plays when it expands or collapses. Ignored under `prefers-reduced-motion`.
80
+ *
81
+ * @cssproperty {pixel} [--c2-dash-card__header--min-height=44px]
82
+ * @cssproperty {padding} [--c2-dash-card__header--padding-block=8px]
83
+ * @cssproperty {padding} [--c2-dash-card__header--padding-inline=12px]
84
+ * @cssproperty {pixel} [--c2-dash-card__header--gap=12px]
85
+ * @cssproperty {background} [--c2-dash-card__header--background=transparent]
86
+ * @cssproperty {border} [--c2-dash-card__header--border-bottom=none]
87
+ * @cssproperty {color} [--c2-dash-card__header--color=#18181b]
88
+ * @cssproperty {pixel} [--c2-dash-card__header--font-size=14px]
89
+ * @cssproperty {font-weight} [--c2-dash-card__header--font-weight=600]
90
+ *
91
+ * @cssproperty {pixel} [--c2-dash-card__footer--min-height=36px]
92
+ * @cssproperty {padding} [--c2-dash-card__footer--padding-block=6px]
93
+ * @cssproperty {padding} [--c2-dash-card__footer--padding-inline=12px]
94
+ * @cssproperty {pixel} [--c2-dash-card__footer--gap=8px]
95
+ * @cssproperty {background} [--c2-dash-card__footer--background=transparent]
96
+ * @cssproperty {border} [--c2-dash-card__footer--border-top=none]
97
+ * @cssproperty {color} [--c2-dash-card__footer--color=#71717a]
98
+ * @cssproperty {pixel} [--c2-dash-card__footer--font-size=12px]
99
+ *
100
+ * @cssproperty {pixel} [--c2-dash-card__actions--gap=8px]
101
+ * @cssproperty {pixel} [--c2-dash-card__controls--gap=2px]
102
+ * @cssproperty {pixel} [--c2-dash-card__controls--offset=4px] - Inset of the floating control group from the
103
+ * top-right corner, used only while the header row is off.
104
+ *
105
+ * @cssproperty {pixel} [--c2-dash-card__control--size=24px]
106
+ * @cssproperty {pixel} [--c2-dash-card__control--icon-size=16px]
107
+ * @cssproperty {border-radius} [--c2-dash-card__control--border-radius=6px]
108
+ * @cssproperty {color} [--c2-dash-card__control--color=#71717a]
109
+ * @cssproperty {background} [--c2-dash-card__control--background=transparent]
110
+ * @cssproperty {background} [--c2-dash-card__control__hover--background=#f4f4f5]
111
+ * @cssproperty {color} [--c2-dash-card__control__hover--color=#18181b]
112
+ * @cssproperty {outline} [--c2-dash-card__control__focus--outline=2px solid rgba(2, 101, 220, 0.4)]
113
+ * @cssproperty {pixel} [--c2-dash-card__control__focus--outline-offset=2px]
114
+ *
115
+ * @cssproperty {pixel} [--c2-dash-card__handle--size=6px] - Thickness of the grab area on an edge. It reaches into
116
+ * the pane, so it stays grabbable however wide the grid gutter is.
117
+ * @cssproperty {pixel} [--c2-dash-card__handle--inset=2px] - Gap between the handle's visible bar and the ends of
118
+ * the edge.
119
+ * @cssproperty {background} [--c2-dash-card__handle--background=transparent]
120
+ * @cssproperty {background} [--c2-dash-card__handle__hover--background=rgba(2, 101, 220, 0.2)]
121
+ * @cssproperty {background} [--c2-dash-card__handle__active--background=rgb(2, 101, 220)]
122
+ * @cssproperty {border-radius} [--c2-dash-card__handle--border-radius=999px]
123
+ * @cssproperty {duration} [--c2-dash-card__handle--transition-duration=150ms]
124
+ * @cssproperty {outline} [--c2-dash-card__handle__focus--outline=2px solid rgba(2, 101, 220, 0.4)]
125
+ */
126
+ export declare class DashCard extends LitElement {
127
+ static styles: import("lit").CSSResult;
128
+ /** Identifier the grid's `layout` record is keyed by. */
129
+ cardId: string | undefined;
130
+ /** 1-based column the card starts in. */
131
+ col: number;
132
+ /** 1-based row the card starts in. */
133
+ row: number;
134
+ /** Number of columns the card occupies. */
135
+ colSpan: number;
136
+ /** Number of rows the card occupies. */
137
+ rowSpan: number;
138
+ /** Smallest width in pixels the card accepts; it raises the minimum of every column it covers. */
139
+ minWidth: number;
140
+ /** Smallest height in pixels the card accepts; it raises the minimum of every row it covers. */
141
+ minHeight: number;
142
+ /** Which edges carry a drag handle. */
143
+ resize: DashCardResize;
144
+ /** Offers a control that expands the card across every column. */
145
+ expandWidth: boolean;
146
+ /** Offers a control that expands the card across every row. */
147
+ expandHeight: boolean;
148
+ /** Offers a control that expands the card over the whole grid. */
149
+ expandFull: boolean;
150
+ /** How far the card is currently expanded. Settable, so an app can expand a card from its own control. */
151
+ expanded: DashCardExpanded;
152
+ private hasHeader;
153
+ private hasActions;
154
+ private hasSlottedControls;
155
+ private hasFooter;
156
+ private dashboard;
157
+ /** The grid reads this during a drag; a hidden card asks for nothing. */
158
+ get constraint(): DashCardConstraint | undefined;
159
+ connectedCallback(): void;
160
+ disconnectedCallback(): void;
161
+ /** Called by the grid when its shape or its layout record changed. */
162
+ hostChanged(): void;
163
+ willUpdate(): void;
164
+ updated(): void;
165
+ /**
166
+ * The splitter values are written straight onto the handles rather than re-rendered: a drag changes them on
167
+ * every pointer move, and nothing else about the card changes with them.
168
+ */
169
+ private syncHandleValues;
170
+ /** `slotchange` does not fire for a server-rendered shadow root, so read every slot once. */
171
+ firstUpdated(): void;
172
+ /**
173
+ * The grid may upgrade after its cards do — the card element is in the document either way, so find it and wait
174
+ * for the definition rather than for a parent that is already alive.
175
+ */
176
+ private attachToDashboard;
177
+ private get placement();
178
+ private handleSlotChange;
179
+ private updateSection;
180
+ private startDrag;
181
+ private handleHandleKeydown;
182
+ private toggleExpanded;
183
+ private renderControl;
184
+ private renderControls;
185
+ private renderHandle;
186
+ private renderHandles;
187
+ render(): TemplateResult<1>;
188
+ }
189
+ declare global {
190
+ interface HTMLElementTagNameMap {
191
+ 'c2-dash-card': DashCard;
192
+ }
193
+ }
194
+ //# sourceMappingURL=dash-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dash-card.d.ts","sourceRoot":"","sources":["../../src/dash-card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAiC,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAKpF,OAAO,KAAK,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAChG,OAAO,EAAiB,KAAK,kBAAkB,EAAsB,MAAM,kBAAkB,CAAA;AAG7F,8CAA8C;AAC9C,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CAAA;AAExE,gEAAgE;AAChE,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEnE,2CAA2C;AAC3C,MAAM,WAAW,0BAA0B;IACzC,+BAA+B;IAC/B,QAAQ,EAAE,gBAAgB,CAAA;CAC3B;AAED,sEAAsE;AACtE,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,WAAW,CAAC,0BAA0B,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,QAAQ;IACvB,gBAAgB,EAAE,qBAAqB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAA;IACnE,mBAAmB,EAAE,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAA;CAC1E;AA0BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGG;AACH,qBACa,QAAS,SAAQ,UAAU;IACtC,OAAgB,MAAM,0BAAoB;IAE1C,yDAAyD;IACrB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAY;IAE1E,yCAAyC;IACb,GAAG,SAAI;IAEnC,sCAAsC;IACV,GAAG,SAAI;IAEnC,2CAA2C;IACQ,OAAO,SAAI;IAE9D,wCAAwC;IACW,OAAO,SAAI;IAE9D,kGAAkG;IAC9C,QAAQ,SAAI;IAEhE,gGAAgG;IAC3C,SAAS,SAAI;IAElE,uCAAuC;IACV,MAAM,EAAE,cAAc,CAAS;IAE5D,kEAAkE;IACV,WAAW,UAAQ;IAE3E,+DAA+D;IACN,YAAY,UAAQ;IAE7E,kEAAkE;IACX,UAAU,UAAQ;IAEzE,0GAA0G;IAC7E,QAAQ,EAAE,gBAAgB,CAAS;IAEvD,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,SAAS,CAAuC;IAEjE,yEAAyE;IACzE,IAAI,UAAU,IAAI,kBAAkB,GAAG,SAAS,CAW/C;IAEQ,iBAAiB;IAKjB,oBAAoB;IAM7B,sEAAsE;IACtE,WAAW;IAIF,UAAU;IAKV,OAAO;IAOhB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB,6FAA6F;IACpF,YAAY;IAIrB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAczB,OAAO,KAAK,SAAS,GAuBpB;IAED,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,SAAS;IA+BjB,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,aAAa;IAwBrB,OAAO,CAAC,cAAc;IAsCtB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,aAAa;IAkBZ,MAAM;CAsBhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,QAAQ,CAAA;KACzB;CACF"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The contract between `c2-dashboard` and `c2-dash-card`. It lives in its own module so neither element has to
3
+ * import the other's class: the grid imports the card to register the tag, the card only imports these types.
4
+ */
5
+ /** Placement of one card, 1-based like CSS grid. Every field overrides the card's own attribute. */
6
+ export interface DashCardPlacement {
7
+ /** 1-based column the card starts in. */
8
+ col?: number;
9
+ /** 1-based row the card starts in. */
10
+ row?: number;
11
+ /** Number of columns the card occupies. */
12
+ colSpan?: number;
13
+ /** Number of rows the card occupies. */
14
+ rowSpan?: number;
15
+ /** `false` hides the card, which is how a layout drops a card without removing it from the markup. */
16
+ visible?: boolean;
17
+ }
18
+ /** What one card contributes to the minimum size of the tracks it covers, read during a drag. */
19
+ export interface DashCardConstraint {
20
+ col: number;
21
+ colSpan: number;
22
+ row: number;
23
+ rowSpan: number;
24
+ minWidth: number;
25
+ minHeight: number;
26
+ }
27
+ /** The part of `c2-dashboard` a card talks to. */
28
+ export interface DashboardHost extends HTMLElement {
29
+ readonly columnCount: number;
30
+ readonly rowCount: number;
31
+ registerCard(card: DashboardCard): void;
32
+ unregisterCard(card: DashboardCard): void;
33
+ /** Resizes one column by `delta` pixels, clamped by the minimums; returns the change that was actually applied. */
34
+ resizeColumn(index: number, delta: number): number;
35
+ /** Resizes one row by `delta` pixels, clamped by the minimums; returns the change that was actually applied. */
36
+ resizeRow(index: number, delta: number): number;
37
+ /** End of a gesture: persist the track sizes and report them. */
38
+ commitResize(): void;
39
+ /** Where the splitter after `index` sits, as a percentage of the tracks' total — the handles' `aria-valuenow`. */
40
+ splitterValue(axis: 'column' | 'row', index: number): number;
41
+ placementOf(cardId: string | undefined): DashCardPlacement | undefined;
42
+ }
43
+ /** The part of `c2-dash-card` the grid reads back. */
44
+ export interface DashboardCard extends HTMLElement {
45
+ /** `undefined` while the card is hidden, so a hidden card constrains nothing. */
46
+ readonly constraint: DashCardConstraint | undefined;
47
+ /** The grid changed shape: re-render the placement and the handles. */
48
+ hostChanged(): void;
49
+ }
50
+ export declare const DASHBOARD_TAG = "c2-dashboard";
51
+ //# sourceMappingURL=dashboard-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-host.d.ts","sourceRoot":"","sources":["../../src/dashboard-host.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAChC,yCAAyC;IACzC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sGAAsG;IACtG,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,iGAAiG;AACjG,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,kDAAkD;AAClD,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAAA;IACvC,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAAA;IACzC,mHAAmH;IACnH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAClD,gHAAgH;IAChH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/C,iEAAiE;IACjE,YAAY,IAAI,IAAI,CAAA;IACpB,kHAAkH;IAClH,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAC5D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,CAAA;CACvE;AAED,sDAAsD;AACtD,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,iFAAiF;IACjF,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACnD,uEAAuE;IACvE,WAAW,IAAI,IAAI,CAAA;CACpB;AAED,eAAO,MAAM,aAAa,iBAAiB,CAAA"}
@@ -0,0 +1,111 @@
1
+ import { LitElement, type PropertyValues } from 'lit';
2
+ import type { TypedAddEventListener, TypedRemoveEventListener } from '@c2n/core/event-helper.js';
3
+ import type { DashCardPlacement, DashboardCard } from './dashboard-host';
4
+ import './dash-card';
5
+ export type { DashCardPlacement } from './dashboard-host';
6
+ /** Track sizes the grid is laid out with. Both are CSS track lists, one entry per track. */
7
+ export interface DashboardLayoutChangeDetail {
8
+ /** Column tracks, e.g. `['320px', '1fr']`. */
9
+ columns: string[];
10
+ /** Row tracks. */
11
+ rows: string[];
12
+ }
13
+ /** Events fired by {@link Dashboard}, keyed for `addEventListener`. */
14
+ export interface DashboardEventMap {
15
+ 'layout-change': CustomEvent<DashboardLayoutChangeDetail>;
16
+ }
17
+ export interface Dashboard {
18
+ addEventListener: TypedAddEventListener<Dashboard, DashboardEventMap>;
19
+ removeEventListener: TypedRemoveEventListener<Dashboard, DashboardEventMap>;
20
+ }
21
+ /**
22
+ * Grid of resizable panes. The children are `c2-dash-card` elements, placed by their `col`/`row` attributes; each
23
+ * one draws drag handles on the edges it shares with a neighbour, and dragging one resizes the whole track, so the
24
+ * cards on either side stay aligned. `columns` and `rows` take a track count (`columns="3"` is three equal columns)
25
+ * or an explicit track list (`columns="320px 1fr"`); a track is switched to pixels the first time it is dragged, and
26
+ * the `fr` tracks around it give up the space in proportion to their weight, down to the minimums. With a
27
+ * `storage-key` the sizes survive a reload.
28
+ *
29
+ * The grid is the sizing authority: a card never sets its own width. Give the element a height (or place it in a
30
+ * flex/grid parent that does) whenever the rows use `fr`, since the row tracks divide the host's height.
31
+ *
32
+ * @tag c2-dashboard
33
+ *
34
+ * @slot - The `c2-dash-card` panes. Anything else becomes an ordinary grid item, placed by your own CSS.
35
+ *
36
+ * @event {CustomEvent<DashboardLayoutChangeDetail>} layout-change - The track sizes changed and the gesture ended.
37
+ * Does not bubble: listen on the element.
38
+ *
39
+ * @cssproperty {pixel} [--c2-dashboard--gap=8px] - Gutter between the cards. The drag handles sit inside the cards,
40
+ * so a gutter under ~6px is hard to grab.
41
+ * @cssproperty {padding} [--c2-dashboard--padding=0px] - Inset around the tracks. With a `background` it is what
42
+ * turns the gutters into a visible frame between the panes.
43
+ * @cssproperty {background} [--c2-dashboard--background=transparent]
44
+ * @cssproperty {border} [--c2-dashboard--border=none]
45
+ * @cssproperty {border-radius} [--c2-dashboard--border-radius=0px]
46
+ */
47
+ export declare class Dashboard extends LitElement {
48
+ static styles: import("lit").CSSResult;
49
+ /** Column count (`3`) or an explicit track list (`'320px 1fr'`). Each entry must be one simple size. */
50
+ columns: number | string | string[];
51
+ /** Row count (`2`) or an explicit track list (`'auto 1fr'`). */
52
+ rows: number | string | string[];
53
+ /** Floor for every column, in pixels. A card's own `min-width` raises it for the tracks that card covers. */
54
+ minColumnWidth: number;
55
+ /** Floor for every row, in pixels. A card's own `min-height` raises it for the tracks that card covers. */
56
+ minRowHeight: number;
57
+ /** `localStorage` key the track sizes are stored under. Without it the grid starts from the authored tracks. */
58
+ storageKey: string | undefined;
59
+ /**
60
+ * Placement overrides keyed by the cards' `card-id`, for a layout that is chosen at runtime rather than authored
61
+ * in the markup. Each entry wins over the card's own `col`/`row`/`col-span`/`row-span`/visibility.
62
+ */
63
+ layout: Record<string, DashCardPlacement> | undefined;
64
+ private columnTracks;
65
+ private rowTracks;
66
+ private cards;
67
+ /** Used track sizes, valid until the next write to the tracks. */
68
+ private pixelCache;
69
+ /** Column tracks as they are laid out right now. */
70
+ get columnSizes(): string[];
71
+ /** Row tracks as they are laid out right now. */
72
+ get rowSizes(): string[];
73
+ get columnCount(): number;
74
+ get rowCount(): number;
75
+ protected willUpdate(changed: PropertyValues): void;
76
+ protected updated(changed: PropertyValues): void;
77
+ /** Back to the authored tracks, and the stored ones are forgotten. */
78
+ reset(): void;
79
+ registerCard(card: DashboardCard): void;
80
+ unregisterCard(card: DashboardCard): void;
81
+ placementOf(cardId: string | undefined): DashCardPlacement | undefined;
82
+ resizeColumn(index: number, delta: number): number;
83
+ resizeRow(index: number, delta: number): number;
84
+ commitResize(): void;
85
+ /**
86
+ * Where the splitter after `index` sits, as a percentage of the tracks' total. A focusable `separator` is a
87
+ * window splitter, and a window splitter has to report a value.
88
+ */
89
+ splitterValue(axis: 'column' | 'row', index: number): number;
90
+ /**
91
+ * Reading a used track size forces layout, and a drag asks for it from every card's handles on every pointer
92
+ * move. The answer cannot change until something writes to the DOM, so it is cached until one of them does.
93
+ */
94
+ private trackPixels;
95
+ /** What is left of the host along `axis` once the tracks, the gutters and the padding have taken their share. */
96
+ private freeSpace;
97
+ private emitLayoutChange;
98
+ private resizeTrack;
99
+ /** The largest minimum any card covering this track asks for, spread over the tracks that card spans. */
100
+ private trackMin;
101
+ private syncTracks;
102
+ private readStored;
103
+ private persist;
104
+ render(): import("lit-html").TemplateResult<1>;
105
+ }
106
+ declare global {
107
+ interface HTMLElementTagNameMap {
108
+ 'c2-dashboard': Dashboard;
109
+ }
110
+ }
111
+ //# sourceMappingURL=dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAmB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAItE,OAAO,KAAK,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAChG,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAIxE,OAAO,aAAa,CAAA;AAEpB,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEzD,4FAA4F;AAC5F,MAAM,WAAW,2BAA2B;IAC1C,8CAA8C;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,kBAAkB;IAClB,IAAI,EAAE,MAAM,EAAE,CAAA;CACf;AAED,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,WAAW,CAAC,2BAA2B,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,SAAS;IACxB,gBAAgB,EAAE,qBAAqB,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAA;IACrE,mBAAmB,EAAE,wBAAwB,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAA;CAC5E;AAiCD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBACa,SAAU,SAAQ,UAAU;IACvC,OAAgB,MAAM,0BAAoB;IAE1C,wGAAwG;IAC5F,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAAI;IAEnD,gEAAgE;IACpD,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAAI;IAEhD,6GAA6G;IAClD,cAAc,SAAK;IAE9E,2GAA2G;IAClD,YAAY,SAAK;IAE1E,gHAAgH;IACxE,UAAU,EAAE,MAAM,GAAG,SAAS,CAAY;IAElF;;;OAGG;IAC6B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG,SAAS,CAAY;IAExF,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,SAAS,CAAe;IAEzC,OAAO,CAAC,KAAK,CAA2B;IAExC,kEAAkE;IAClE,OAAO,CAAC,UAAU,CAAwC;IAE1D,oDAAoD;IACpD,IAAI,WAAW,IAAI,MAAM,EAAE,CAE1B;IAED,iDAAiD;IACjD,IAAI,QAAQ,IAAI,MAAM,EAAE,CAEvB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;cAEkB,UAAU,CAAC,OAAO,EAAE,cAAc;cAIlC,OAAO,CAAC,OAAO,EAAE,cAAc;IAUlD,sEAAsE;IACtE,KAAK;IAaL,YAAY,CAAC,IAAI,EAAE,aAAa;IAKhC,cAAc,CAAC,IAAI,EAAE,aAAa;IAIlC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS;IAItE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAQlD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAQ/C,YAAY;IAKZ;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAS5D;;;OAGG;IACH,OAAO,CAAC,WAAW;IAYnB,iHAAiH;IACjH,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,WAAW;IAkCnB,yGAAyG;IACzG,OAAO,CAAC,QAAQ;IAchB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,OAAO;IASN,MAAM;CAGhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,SAAS,CAAA;KAC1B;CACF"}
package/vue.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ // GENERATED by @c2n/framework-types. Do not edit by hand.
2
+ //
3
+ // Vue template types for the custom elements of @c2n/dashboard. Import once, anywhere in the program:
4
+ //
5
+ // import '@c2n/dashboard/vue'
6
+ //
7
+ // Tell the compiler about the tags as well, or every c2-* tag is resolved as a Vue component and renders
8
+ // nothing: template.compilerOptions.isCustomElement = (tag) => tag.startsWith('c2-') in vite.config.ts.
9
+
10
+ import type { DefineComponent, HTMLAttributes } from 'vue'
11
+ import type { DashCard, DashCardEventMap } from '@c2n/dashboard/dash-card.js'
12
+ import type { Dashboard, DashboardEventMap } from '@c2n/dashboard'
13
+
14
+ /** The element's own public properties, plus every attribute Vue understands on a host element. */
15
+ type C2Props<T> = Partial<Omit<T, keyof HTMLElement>> & HTMLAttributes
16
+
17
+ declare module 'vue' {
18
+ interface GlobalComponents {
19
+ 'c2-dash-card': DefineComponent<
20
+ C2Props<DashCard> & {
21
+ 'card-id'?: unknown
22
+ 'col-span'?: unknown
23
+ 'row-span'?: unknown
24
+ 'min-width'?: unknown
25
+ 'min-height'?: unknown
26
+ 'expand-width'?: unknown
27
+ 'expand-height'?: unknown
28
+ 'expand-full'?: unknown
29
+ onExpandChange?: (event: DashCardEventMap['expand-change']) => void
30
+ }
31
+ >
32
+ 'c2-dashboard': DefineComponent<
33
+ C2Props<Dashboard> & {
34
+ 'min-column-width'?: unknown
35
+ 'min-row-height'?: unknown
36
+ 'storage-key'?: unknown
37
+ onLayoutChange?: (event: DashboardEventMap['layout-change']) => void
38
+ }
39
+ >
40
+ }
41
+ }
42
+
43
+ declare module '@vue/runtime-dom' {
44
+ interface HTMLAttributes {
45
+ /** Vue's own definition omits it, and slotting a plain element into a component needs it. */
46
+ slot?: string
47
+ }
48
+ }
49
+
50
+ export {}
package/vue.js ADDED
@@ -0,0 +1,3 @@
1
+ // GENERATED by @c2n/framework-types. Do not edit by hand.
2
+ // Types only — see the matching .d.ts.
3
+ export {}