@inshorts/glint-ui 0.5.4 → 0.6.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/README.md +3 -1
- package/dist/alert.cjs +96 -0
- package/dist/alert.cjs.map +1 -0
- package/dist/alert.d.cts +29 -0
- package/dist/alert.d.ts +29 -0
- package/dist/alert.js +10 -0
- package/dist/alert.js.map +1 -0
- package/dist/autocomplete.cjs +578 -0
- package/dist/autocomplete.cjs.map +1 -0
- package/dist/autocomplete.d.cts +56 -0
- package/dist/autocomplete.d.ts +56 -0
- package/dist/autocomplete.js +11 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/badge.cjs +89 -0
- package/dist/badge.cjs.map +1 -0
- package/dist/badge.d.cts +25 -0
- package/dist/badge.d.ts +25 -0
- package/dist/badge.js +10 -0
- package/dist/badge.js.map +1 -0
- package/dist/breadcrumb.cjs +88 -0
- package/dist/breadcrumb.cjs.map +1 -0
- package/dist/breadcrumb.d.cts +14 -0
- package/dist/breadcrumb.d.ts +14 -0
- package/dist/breadcrumb.js +8 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/button.cjs +143 -0
- package/dist/button.cjs.map +1 -0
- package/dist/button.d.cts +34 -0
- package/dist/button.d.ts +34 -0
- package/dist/button.js +9 -0
- package/dist/button.js.map +1 -0
- package/dist/card.cjs +111 -0
- package/dist/card.cjs.map +1 -0
- package/dist/card.d.cts +15 -0
- package/dist/card.d.ts +15 -0
- package/dist/card.js +18 -0
- package/dist/card.js.map +1 -0
- package/dist/checkbox.cjs +66 -0
- package/dist/checkbox.cjs.map +1 -0
- package/{src/checkbox.tsx → dist/checkbox.d.cts} +5 -22
- package/dist/checkbox.d.ts +25 -0
- package/dist/checkbox.js +10 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-43ZUFY63.js +85 -0
- package/dist/chunk-43ZUFY63.js.map +1 -0
- package/dist/chunk-67ACNS42.js +29 -0
- package/dist/chunk-67ACNS42.js.map +1 -0
- package/dist/chunk-6URKHMCF.js +76 -0
- package/dist/chunk-6URKHMCF.js.map +1 -0
- package/dist/chunk-ASU6CUTZ.js +85 -0
- package/dist/chunk-ASU6CUTZ.js.map +1 -0
- package/dist/chunk-EWSBFU54.js +53 -0
- package/dist/chunk-EWSBFU54.js.map +1 -0
- package/dist/chunk-G4ZQUSAJ.js +38 -0
- package/dist/chunk-G4ZQUSAJ.js.map +1 -0
- package/dist/chunk-GMCUXEFI.js +67 -0
- package/dist/chunk-GMCUXEFI.js.map +1 -0
- package/dist/chunk-HBBOR43A.js +85 -0
- package/dist/chunk-HBBOR43A.js.map +1 -0
- package/dist/chunk-HCA5ODCD.js +11 -0
- package/dist/chunk-HCA5ODCD.js.map +1 -0
- package/dist/chunk-HYRJYUPI.js +26 -0
- package/dist/chunk-HYRJYUPI.js.map +1 -0
- package/dist/chunk-IRCTGJQB.js +26 -0
- package/dist/chunk-IRCTGJQB.js.map +1 -0
- package/dist/chunk-JDE5XYPT.js +30 -0
- package/dist/chunk-JDE5XYPT.js.map +1 -0
- package/dist/chunk-JSJEEKR3.js +59 -0
- package/dist/chunk-JSJEEKR3.js.map +1 -0
- package/dist/chunk-KR3BCFP7.js +85 -0
- package/dist/chunk-KR3BCFP7.js.map +1 -0
- package/dist/chunk-MQT5RAQV.js +202 -0
- package/dist/chunk-MQT5RAQV.js.map +1 -0
- package/dist/chunk-MUPW2TFJ.js +102 -0
- package/dist/chunk-MUPW2TFJ.js.map +1 -0
- package/dist/chunk-PM2VE7DJ.js +100 -0
- package/dist/chunk-PM2VE7DJ.js.map +1 -0
- package/dist/chunk-PPYR3O3L.js +44 -0
- package/dist/chunk-PPYR3O3L.js.map +1 -0
- package/dist/chunk-PUTC52SI.js +98 -0
- package/dist/chunk-PUTC52SI.js.map +1 -0
- package/dist/chunk-PVK4WQJZ.js +513 -0
- package/dist/chunk-PVK4WQJZ.js.map +1 -0
- package/dist/chunk-PWAKTEXF.js +18 -0
- package/dist/chunk-PWAKTEXF.js.map +1 -0
- package/dist/chunk-QTFWORA7.js +26 -0
- package/dist/chunk-QTFWORA7.js.map +1 -0
- package/dist/chunk-RUPL2H6V.js +49 -0
- package/dist/chunk-RUPL2H6V.js.map +1 -0
- package/dist/chunk-S7PVZAPL.js +28 -0
- package/dist/chunk-S7PVZAPL.js.map +1 -0
- package/dist/chunk-SQDV2AKJ.js +49 -0
- package/dist/chunk-SQDV2AKJ.js.map +1 -0
- package/dist/chunk-T2KXVH3O.js +297 -0
- package/dist/chunk-T2KXVH3O.js.map +1 -0
- package/dist/chunk-T4WCCHJQ.js +29 -0
- package/dist/chunk-T4WCCHJQ.js.map +1 -0
- package/dist/chunk-UC3ZIQPE.js +93 -0
- package/dist/chunk-UC3ZIQPE.js.map +1 -0
- package/dist/chunk-UE7JY7VR.js +28 -0
- package/dist/chunk-UE7JY7VR.js.map +1 -0
- package/dist/chunk-WVNRFO5F.js +70 -0
- package/dist/chunk-WVNRFO5F.js.map +1 -0
- package/dist/chunk-Y5WCT6XN.js +93 -0
- package/dist/chunk-Y5WCT6XN.js.map +1 -0
- package/dist/chunk-YDICVETX.js +151 -0
- package/dist/chunk-YDICVETX.js.map +1 -0
- package/dist/chunk-YSU4VWRD.js +90 -0
- package/dist/chunk-YSU4VWRD.js.map +1 -0
- package/dist/chunk-ZFSQVMLJ.js +126 -0
- package/dist/chunk-ZFSQVMLJ.js.map +1 -0
- package/dist/chunk-ZHATD73Y.js +46 -0
- package/dist/chunk-ZHATD73Y.js.map +1 -0
- package/dist/chunk-ZLAA4DGG.js +111 -0
- package/dist/chunk-ZLAA4DGG.js.map +1 -0
- package/dist/chunk-ZP252OJZ.js +56 -0
- package/dist/chunk-ZP252OJZ.js.map +1 -0
- package/dist/clipboard.cjs +125 -0
- package/dist/clipboard.cjs.map +1 -0
- package/dist/clipboard.d.cts +52 -0
- package/dist/clipboard.d.ts +52 -0
- package/dist/clipboard.js +23 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/collapsible.cjs +94 -0
- package/dist/collapsible.cjs.map +1 -0
- package/dist/collapsible.d.cts +21 -0
- package/dist/collapsible.d.ts +21 -0
- package/dist/collapsible.js +9 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/confirm-dialog.cjs +369 -0
- package/dist/confirm-dialog.cjs.map +1 -0
- package/dist/confirm-dialog.d.cts +16 -0
- package/dist/confirm-dialog.d.ts +16 -0
- package/dist/confirm-dialog.js +13 -0
- package/dist/confirm-dialog.js.map +1 -0
- package/dist/copy-button.cjs +102 -0
- package/dist/copy-button.cjs.map +1 -0
- package/dist/copy-button.d.cts +18 -0
- package/dist/copy-button.d.ts +18 -0
- package/dist/copy-button.js +10 -0
- package/dist/copy-button.js.map +1 -0
- package/dist/data-table.cjs +140 -0
- package/dist/data-table.cjs.map +1 -0
- package/dist/data-table.d.cts +81 -0
- package/dist/data-table.d.ts +81 -0
- package/dist/data-table.js +8 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-range-picker/dateRange.cjs +138 -0
- package/dist/date-range-picker/dateRange.cjs.map +1 -0
- package/dist/date-range-picker/dateRange.d.cts +57 -0
- package/dist/date-range-picker/dateRange.d.ts +57 -0
- package/dist/date-range-picker/dateRange.js +31 -0
- package/dist/date-range-picker/dateRange.js.map +1 -0
- package/dist/date-range-picker/index.cjs +375 -0
- package/dist/date-range-picker/index.cjs.map +1 -0
- package/dist/date-range-picker/index.d.cts +18 -0
- package/dist/date-range-picker/index.d.ts +18 -0
- package/dist/date-range-picker/index.js +10 -0
- package/dist/date-range-picker/index.js.map +1 -0
- package/dist/dialogLayer.cjs +121 -0
- package/dist/dialogLayer.cjs.map +1 -0
- package/dist/dialogLayer.d.cts +11 -0
- package/dist/dialogLayer.d.ts +11 -0
- package/dist/dialogLayer.js +8 -0
- package/dist/dialogLayer.js.map +1 -0
- package/dist/feature-access-gate.cjs +160 -0
- package/dist/feature-access-gate.cjs.map +1 -0
- package/dist/feature-access-gate.d.cts +14 -0
- package/dist/feature-access-gate.d.ts +14 -0
- package/dist/feature-access-gate.js +11 -0
- package/dist/feature-access-gate.js.map +1 -0
- package/dist/field.cjs +58 -0
- package/dist/field.cjs.map +1 -0
- package/dist/field.d.cts +23 -0
- package/dist/field.d.ts +23 -0
- package/dist/field.js +8 -0
- package/dist/field.js.map +1 -0
- package/dist/image-uploader.cjs +293 -0
- package/dist/image-uploader.cjs.map +1 -0
- package/dist/image-uploader.d.cts +34 -0
- package/dist/image-uploader.d.ts +34 -0
- package/dist/image-uploader.js +12 -0
- package/dist/image-uploader.js.map +1 -0
- package/dist/index.cjs +3012 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/input.cjs +58 -0
- package/dist/input.cjs.map +1 -0
- package/dist/input.d.cts +7 -0
- package/dist/input.d.ts +7 -0
- package/dist/input.js +10 -0
- package/dist/input.js.map +1 -0
- package/dist/label.cjs +67 -0
- package/dist/label.cjs.map +1 -0
- package/dist/label.d.cts +7 -0
- package/dist/label.d.ts +7 -0
- package/dist/label.js +9 -0
- package/dist/label.js.map +1 -0
- package/dist/modal.cjs +212 -0
- package/dist/modal.cjs.map +1 -0
- package/dist/modal.d.cts +39 -0
- package/dist/modal.d.ts +39 -0
- package/dist/modal.js +12 -0
- package/dist/modal.js.map +1 -0
- package/dist/page-header.cjs +78 -0
- package/dist/page-header.cjs.map +1 -0
- package/dist/page-header.d.cts +35 -0
- package/dist/page-header.d.ts +35 -0
- package/dist/page-header.js +8 -0
- package/dist/page-header.js.map +1 -0
- package/dist/pagination.cjs +237 -0
- package/dist/pagination.cjs.map +1 -0
- package/dist/pagination.d.cts +15 -0
- package/dist/pagination.d.ts +15 -0
- package/dist/pagination.js +11 -0
- package/dist/pagination.js.map +1 -0
- package/dist/panel-brand-mark.cjs +59 -0
- package/dist/panel-brand-mark.cjs.map +1 -0
- package/dist/panel-brand-mark.d.cts +13 -0
- package/dist/panel-brand-mark.d.ts +13 -0
- package/dist/panel-brand-mark.js +8 -0
- package/dist/panel-brand-mark.js.map +1 -0
- package/dist/panel-sign-in.cjs +256 -0
- package/dist/panel-sign-in.cjs.map +1 -0
- package/dist/panel-sign-in.d.cts +31 -0
- package/dist/panel-sign-in.d.ts +31 -0
- package/dist/panel-sign-in.js +14 -0
- package/dist/panel-sign-in.js.map +1 -0
- package/dist/segmented-control.cjs +126 -0
- package/dist/segmented-control.cjs.map +1 -0
- package/dist/segmented-control.d.cts +60 -0
- package/dist/segmented-control.d.ts +60 -0
- package/dist/segmented-control.js +9 -0
- package/dist/segmented-control.js.map +1 -0
- package/dist/select.cjs +648 -0
- package/dist/select.cjs.map +1 -0
- package/dist/select.d.cts +30 -0
- package/dist/select.d.ts +30 -0
- package/dist/select.js +12 -0
- package/dist/select.js.map +1 -0
- package/dist/separator.cjs +70 -0
- package/dist/separator.cjs.map +1 -0
- package/dist/separator.d.cts +13 -0
- package/dist/separator.d.ts +13 -0
- package/dist/separator.js +9 -0
- package/dist/separator.js.map +1 -0
- package/dist/side-drawer.cjs +321 -0
- package/dist/side-drawer.cjs.map +1 -0
- package/dist/side-drawer.d.cts +21 -0
- package/dist/side-drawer.d.ts +21 -0
- package/dist/side-drawer.js +13 -0
- package/dist/side-drawer.js.map +1 -0
- package/dist/spinner.cjs +57 -0
- package/dist/spinner.cjs.map +1 -0
- package/{src/spinner.tsx → dist/spinner.d.cts} +11 -22
- package/dist/spinner.d.ts +27 -0
- package/dist/spinner.js +8 -0
- package/dist/spinner.js.map +1 -0
- package/dist/spreadsheet-grid.cjs +238 -0
- package/dist/spreadsheet-grid.cjs.map +1 -0
- package/dist/spreadsheet-grid.d.cts +48 -0
- package/dist/spreadsheet-grid.d.ts +48 -0
- package/dist/spreadsheet-grid.js +8 -0
- package/dist/spreadsheet-grid.js.map +1 -0
- package/dist/state-panel.cjs +86 -0
- package/dist/state-panel.cjs.map +1 -0
- package/dist/state-panel.d.cts +23 -0
- package/dist/state-panel.d.ts +23 -0
- package/dist/state-panel.js +9 -0
- package/dist/state-panel.js.map +1 -0
- package/dist/switch.cjs +87 -0
- package/dist/switch.cjs.map +1 -0
- package/dist/switch.d.cts +13 -0
- package/dist/switch.d.ts +13 -0
- package/dist/switch.js +9 -0
- package/dist/switch.js.map +1 -0
- package/dist/table.cjs +139 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +16 -0
- package/dist/table.d.ts +16 -0
- package/dist/table.js +22 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.cjs +168 -0
- package/dist/tabs.cjs.map +1 -0
- package/dist/tabs.d.cts +59 -0
- package/dist/tabs.d.ts +59 -0
- package/dist/tabs.js +11 -0
- package/dist/tabs.js.map +1 -0
- package/dist/textarea.cjs +66 -0
- package/dist/textarea.cjs.map +1 -0
- package/dist/textarea.d.cts +18 -0
- package/dist/textarea.d.ts +18 -0
- package/dist/textarea.js +10 -0
- package/dist/textarea.js.map +1 -0
- package/dist/utils.cjs +35 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +5 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/dist/utils.js.map +1 -0
- package/package.json +390 -13
- package/src/alert.tsx +0 -74
- package/src/autocomplete.tsx +0 -666
- package/src/badge.tsx +0 -60
- package/src/breadcrumb.tsx +0 -71
- package/src/button.tsx +0 -113
- package/src/card.tsx +0 -79
- package/src/clipboard.ts +0 -144
- package/src/collapsible.tsx +0 -63
- package/src/confirm-dialog.tsx +0 -84
- package/src/copy-button.tsx +0 -58
- package/src/data-table.tsx +0 -190
- package/src/date-range-picker/dateRange.ts +0 -142
- package/src/date-range-picker/index.tsx +0 -366
- package/src/dialogLayer.ts +0 -142
- package/src/feature-access-gate.tsx +0 -38
- package/src/field.tsx +0 -56
- package/src/image-uploader.tsx +0 -181
- package/src/index.ts +0 -37
- package/src/input.tsx +0 -16
- package/src/label.tsx +0 -23
- package/src/modal.tsx +0 -117
- package/src/page-header.tsx +0 -75
- package/src/pagination.tsx +0 -116
- package/src/panel-brand-mark.tsx +0 -32
- package/src/panel-sign-in.tsx +0 -134
- package/src/segmented-control.tsx +0 -155
- package/src/select.tsx +0 -105
- package/src/separator.tsx +0 -32
- package/src/side-drawer.tsx +0 -87
- package/src/spreadsheet-grid.tsx +0 -264
- package/src/state-panel.tsx +0 -46
- package/src/switch.tsx +0 -47
- package/src/table.tsx +0 -110
- package/src/tabs.tsx +0 -201
- package/src/textarea.tsx +0 -32
- package/src/utils.ts +0 -6
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-HCA5ODCD.js";
|
|
4
|
+
|
|
5
|
+
// src/tabs.tsx
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
function TabPanel({
|
|
9
|
+
id,
|
|
10
|
+
labelledBy,
|
|
11
|
+
active,
|
|
12
|
+
children,
|
|
13
|
+
className
|
|
14
|
+
}) {
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
role: "tabpanel",
|
|
19
|
+
id,
|
|
20
|
+
"aria-labelledby": labelledBy,
|
|
21
|
+
hidden: !active,
|
|
22
|
+
className,
|
|
23
|
+
children: active ? children : null
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function Tabs({
|
|
28
|
+
tabs,
|
|
29
|
+
value,
|
|
30
|
+
onChange,
|
|
31
|
+
ariaLabel,
|
|
32
|
+
className
|
|
33
|
+
}) {
|
|
34
|
+
const refs = React.useRef(/* @__PURE__ */ new Map());
|
|
35
|
+
const reactId = React.useId();
|
|
36
|
+
const hasInlinePanels = tabs.some((tab) => tab.panel != null);
|
|
37
|
+
function tabDomId(key) {
|
|
38
|
+
return `${reactId}-tab-${key}`;
|
|
39
|
+
}
|
|
40
|
+
function panelDomId(tab) {
|
|
41
|
+
return tab.panelId ?? `${reactId}-panel-${tab.key}`;
|
|
42
|
+
}
|
|
43
|
+
function focusTab(key) {
|
|
44
|
+
onChange(key);
|
|
45
|
+
refs.current.get(key)?.focus();
|
|
46
|
+
}
|
|
47
|
+
function onKeyDown(event) {
|
|
48
|
+
const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
|
|
49
|
+
if (step === 0) return;
|
|
50
|
+
const selectable = tabs.filter((tab) => !tab.disabled);
|
|
51
|
+
if (selectable.length < 2) return;
|
|
52
|
+
const current = selectable.findIndex((tab) => tab.key === value);
|
|
53
|
+
const next = selectable[(current + step + selectable.length) % selectable.length];
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
focusTab(next.key);
|
|
56
|
+
}
|
|
57
|
+
const tablist = /* @__PURE__ */ jsx(
|
|
58
|
+
"div",
|
|
59
|
+
{
|
|
60
|
+
role: "tablist",
|
|
61
|
+
"aria-label": ariaLabel,
|
|
62
|
+
onKeyDown,
|
|
63
|
+
className: cn(
|
|
64
|
+
"border-border flex flex-wrap gap-1 border-b",
|
|
65
|
+
!hasInlinePanels ? className : void 0
|
|
66
|
+
),
|
|
67
|
+
children: tabs.map((tab) => {
|
|
68
|
+
const active = tab.key === value;
|
|
69
|
+
const id = tabDomId(tab.key);
|
|
70
|
+
const controls = tab.panel != null || tab.panelId ? panelDomId(tab) : void 0;
|
|
71
|
+
return /* @__PURE__ */ jsxs(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
id,
|
|
75
|
+
ref: (node) => {
|
|
76
|
+
refs.current.set(tab.key, node);
|
|
77
|
+
},
|
|
78
|
+
type: "button",
|
|
79
|
+
role: "tab",
|
|
80
|
+
"aria-selected": active,
|
|
81
|
+
"aria-controls": controls,
|
|
82
|
+
disabled: tab.disabled,
|
|
83
|
+
tabIndex: active ? 0 : -1,
|
|
84
|
+
onClick: () => onChange(tab.key),
|
|
85
|
+
className: cn(
|
|
86
|
+
"cursor-pointer rounded-t-md px-3 py-1.5 text-sm font-medium transition",
|
|
87
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
|
|
88
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
89
|
+
// The filled-pill active state the five hand-rolled strips
|
|
90
|
+
// already used. Adopting it keeps this a deduplication rather
|
|
91
|
+
// than a restyle of every panel that has tabs.
|
|
92
|
+
active ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
|
|
93
|
+
),
|
|
94
|
+
children: [
|
|
95
|
+
tab.label,
|
|
96
|
+
tab.badge != null ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground ml-1.5 text-xs", children: tab.badge }) : null
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
tab.key
|
|
100
|
+
);
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
if (!hasInlinePanels) return tablist;
|
|
105
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
106
|
+
tablist,
|
|
107
|
+
tabs.map(
|
|
108
|
+
(tab) => tab.panel != null ? /* @__PURE__ */ jsx(
|
|
109
|
+
TabPanel,
|
|
110
|
+
{
|
|
111
|
+
id: panelDomId(tab),
|
|
112
|
+
labelledBy: tabDomId(tab.key),
|
|
113
|
+
active: tab.key === value,
|
|
114
|
+
children: tab.panel
|
|
115
|
+
},
|
|
116
|
+
tab.key
|
|
117
|
+
) : null
|
|
118
|
+
)
|
|
119
|
+
] });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
TabPanel,
|
|
124
|
+
Tabs
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=chunk-ZFSQVMLJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tabs.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The tab strip five Managers were each drawing by hand, with the same\n * `rounded-t-md px-3 py-1.5 text-sm font-medium` and their own idea of what an\n * active tab looks like.\n *\n * It is a controlled component with no state of its own: every caller already\n * held the active key, usually because a URL or a query depends on it.\n *\n * Roles are the reason to use this rather than styling buttons again. A hand\n * -rolled strip is a row of buttons to a screen reader, which says nothing\n * about how many tabs there are or which is selected; `tablist` / `tab` /\n * `aria-selected` do. Arrow-key navigation comes with that expectation, so it\n * is implemented here too.\n *\n * Optional `panel` on a tab (or a sibling `TabPanel`) wires APG `aria-controls`\n * / `role=\"tabpanel\"`. Callers that already render content from `value` can\n * keep doing so and pass `panelId` for association, or switch to `panel`.\n */\nexport interface TabItem<Key extends string = string> {\n key: Key;\n label: React.ReactNode;\n /** Rendered after the label — a count, usually. */\n badge?: React.ReactNode;\n disabled?: boolean;\n /**\n * Optional panel body. When any tab provides `panel`, panels render under\n * the strip with `role=\"tabpanel\"`.\n */\n panel?: React.ReactNode;\n /**\n * External panel element id for `aria-controls` when panels live outside\n * this component.\n */\n panelId?: string;\n}\n\nexport interface TabsProps<Key extends string = string> {\n tabs: ReadonlyArray<TabItem<Key>>;\n value: Key;\n onChange: (key: Key) => void;\n /** Names the strip for screen readers. */\n ariaLabel?: string;\n className?: string;\n}\n\nexport interface TabPanelProps {\n /** Must match the controlling tab's generated or explicit id. */\n id: string;\n /** Id of the tab that labels this panel. */\n labelledBy: string;\n active: boolean;\n children: React.ReactNode;\n className?: string;\n}\n\n/** Companion panel for callers that keep content outside `Tabs`. */\nexport function TabPanel({\n id,\n labelledBy,\n active,\n children,\n className,\n}: TabPanelProps) {\n return (\n <div\n role=\"tabpanel\"\n id={id}\n aria-labelledby={labelledBy}\n hidden={!active}\n className={className}\n >\n {active ? children : null}\n </div>\n );\n}\n\nexport function Tabs<Key extends string = string>({\n tabs,\n value,\n onChange,\n ariaLabel,\n className,\n}: TabsProps<Key>) {\n const refs = React.useRef(new Map<Key, HTMLButtonElement | null>());\n const reactId = React.useId();\n const hasInlinePanels = tabs.some((tab) => tab.panel != null);\n\n function tabDomId(key: Key) {\n return `${reactId}-tab-${key}`;\n }\n\n function panelDomId(tab: TabItem<Key>) {\n return tab.panelId ?? `${reactId}-panel-${tab.key}`;\n }\n\n function focusTab(key: Key) {\n onChange(key);\n // The newly selected tab has to take focus, or the next arrow press is\n // still measured from the old one.\n refs.current.get(key)?.focus();\n }\n\n function onKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {\n const step =\n event.key === \"ArrowRight\" ? 1 : event.key === \"ArrowLeft\" ? -1 : 0;\n if (step === 0) return;\n\n const selectable = tabs.filter((tab) => !tab.disabled);\n if (selectable.length < 2) return;\n\n const current = selectable.findIndex((tab) => tab.key === value);\n // Wraps, which is what a tablist is expected to do.\n const next =\n selectable[(current + step + selectable.length) % selectable.length];\n\n event.preventDefault();\n focusTab(next.key);\n }\n\n const tablist = (\n <div\n role=\"tablist\"\n aria-label={ariaLabel}\n onKeyDown={onKeyDown}\n className={cn(\n \"border-border flex flex-wrap gap-1 border-b\",\n !hasInlinePanels ? className : undefined,\n )}\n >\n {tabs.map((tab) => {\n const active = tab.key === value;\n const id = tabDomId(tab.key);\n const controls =\n tab.panel != null || tab.panelId ? panelDomId(tab) : undefined;\n return (\n <button\n key={tab.key}\n id={id}\n ref={(node) => {\n refs.current.set(tab.key, node);\n }}\n type=\"button\"\n role=\"tab\"\n aria-selected={active}\n aria-controls={controls}\n disabled={tab.disabled}\n // Only the active tab is reachable by Tab; arrows move within the\n // strip. Without this a keyboard user pages through every tab to\n // get past the strip.\n tabIndex={active ? 0 : -1}\n onClick={() => onChange(tab.key)}\n className={cn(\n \"cursor-pointer rounded-t-md px-3 py-1.5 text-sm font-medium transition\",\n \"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n // The filled-pill active state the five hand-rolled strips\n // already used. Adopting it keeps this a deduplication rather\n // than a restyle of every panel that has tabs.\n active\n ? \"bg-primary text-primary-foreground\"\n : \"text-muted-foreground hover:bg-muted\",\n )}\n >\n {tab.label}\n {tab.badge != null ? (\n <span className=\"text-muted-foreground ml-1.5 text-xs\">\n {tab.badge}\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n );\n\n if (!hasInlinePanels) return tablist;\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {tablist}\n {tabs.map((tab) =>\n tab.panel != null ? (\n <TabPanel\n key={tab.key}\n id={panelDomId(tab)}\n labelledBy={tabDomId(tab.key)}\n active={tab.key === value}\n >\n {tab.panel}\n </TabPanel>\n ) : null,\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-HCA5ODCD.js";
|
|
4
|
+
|
|
5
|
+
// src/switch.tsx
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
var Switch = React.forwardRef(
|
|
9
|
+
({ checked, onCheckedChange, className, disabled, id, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
10
|
+
"button",
|
|
11
|
+
{
|
|
12
|
+
ref,
|
|
13
|
+
id,
|
|
14
|
+
type: "button",
|
|
15
|
+
role: "switch",
|
|
16
|
+
"aria-checked": checked,
|
|
17
|
+
disabled,
|
|
18
|
+
onClick: () => onCheckedChange(!checked),
|
|
19
|
+
className: cn(
|
|
20
|
+
// 24×44px track meets DESIGN.md 24px minimum target size.
|
|
21
|
+
"relative inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border transition-colors",
|
|
22
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
|
|
23
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
24
|
+
checked ? "border-primary bg-primary" : "border-border bg-muted",
|
|
25
|
+
className
|
|
26
|
+
),
|
|
27
|
+
...props,
|
|
28
|
+
children: /* @__PURE__ */ jsx(
|
|
29
|
+
"span",
|
|
30
|
+
{
|
|
31
|
+
"aria-hidden": true,
|
|
32
|
+
className: cn(
|
|
33
|
+
"bg-background pointer-events-none block size-5 rounded-full shadow-sm transition-transform",
|
|
34
|
+
checked ? "translate-x-5" : "translate-x-0.5"
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
Switch.displayName = "Switch";
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
Switch
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=chunk-ZHATD73Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/switch.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\nexport type SwitchProps = Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n \"onChange\"\n> & {\n checked: boolean;\n onCheckedChange: (checked: boolean) => void;\n};\n\n/** Compact accessible toggle — use instead of full-width checkbox inputs. */\nexport const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(\n ({ checked, onCheckedChange, className, disabled, id, ...props }, ref) => (\n <button\n ref={ref}\n id={id}\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n disabled={disabled}\n onClick={() => onCheckedChange(!checked)}\n className={cn(\n // 24×44px track meets DESIGN.md 24px minimum target size.\n \"relative inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border transition-colors\",\n \"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n checked ? \"border-primary bg-primary\" : \"border-border bg-muted\",\n className,\n )}\n {...props}\n >\n <span\n aria-hidden\n className={cn(\n \"bg-background pointer-events-none block size-5 rounded-full shadow-sm transition-transform\",\n checked ? \"translate-x-5\" : \"translate-x-0.5\",\n )}\n />\n </button>\n ),\n);\n\nSwitch.displayName = \"Switch\";\n"],"mappings":";;;;;AAEA,YAAY,WAAW;AAiCjB;AApBC,IAAM,SAAe;AAAA,EAC1B,CAAC,EAAE,SAAS,iBAAiB,WAAW,UAAU,IAAI,GAAG,MAAM,GAAG,QAChE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,MAAK;AAAA,MACL,MAAK;AAAA,MACL,gBAAc;AAAA,MACd;AAAA,MACA,SAAS,MAAM,gBAAgB,CAAC,OAAO;AAAA,MACvC,WAAW;AAAA;AAAA,QAET;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,8BAA8B;AAAA,QACxC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC;AAAA;AAAA,UACC,eAAW;AAAA,UACX,WAAW;AAAA,YACT;AAAA,YACA,UAAU,kBAAkB;AAAA,UAC9B;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;AAEA,OAAO,cAAc;","names":[]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-HCA5ODCD.js";
|
|
4
|
+
|
|
5
|
+
// src/data-table.tsx
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
var HEADER_VARIANTS = {
|
|
8
|
+
uppercase: "border-border bg-muted/40 text-muted-foreground border-b text-xs tracking-wide uppercase",
|
|
9
|
+
plain: "text-muted-foreground border-border border-b text-left"
|
|
10
|
+
};
|
|
11
|
+
var DENSITY = {
|
|
12
|
+
comfortable: "px-4 py-3",
|
|
13
|
+
compact: "px-3 py-2"
|
|
14
|
+
};
|
|
15
|
+
var ALIGN = {
|
|
16
|
+
left: "text-left",
|
|
17
|
+
right: "text-right",
|
|
18
|
+
center: "text-center"
|
|
19
|
+
};
|
|
20
|
+
function DataTable({
|
|
21
|
+
columns,
|
|
22
|
+
rows,
|
|
23
|
+
rowKey,
|
|
24
|
+
variant = "uppercase",
|
|
25
|
+
density = "comfortable",
|
|
26
|
+
stickyHeader = false,
|
|
27
|
+
rowClassName,
|
|
28
|
+
emptyMessage,
|
|
29
|
+
footer,
|
|
30
|
+
ariaLabel,
|
|
31
|
+
className,
|
|
32
|
+
tableClassName
|
|
33
|
+
}) {
|
|
34
|
+
return /* @__PURE__ */ jsx(
|
|
35
|
+
"div",
|
|
36
|
+
{
|
|
37
|
+
className: cn(
|
|
38
|
+
"border-border bg-background/80 overflow-x-auto rounded-lg border",
|
|
39
|
+
className
|
|
40
|
+
),
|
|
41
|
+
children: /* @__PURE__ */ jsxs(
|
|
42
|
+
"table",
|
|
43
|
+
{
|
|
44
|
+
"aria-label": ariaLabel,
|
|
45
|
+
className: cn("min-w-full text-left text-sm", tableClassName),
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
"thead",
|
|
49
|
+
{
|
|
50
|
+
className: cn(
|
|
51
|
+
HEADER_VARIANTS[variant],
|
|
52
|
+
stickyHeader && "bg-muted sticky top-0 z-10"
|
|
53
|
+
),
|
|
54
|
+
children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx(
|
|
55
|
+
"th",
|
|
56
|
+
{
|
|
57
|
+
scope: "col",
|
|
58
|
+
"aria-sort": column.ariaSort,
|
|
59
|
+
className: cn(
|
|
60
|
+
DENSITY[density],
|
|
61
|
+
"font-medium",
|
|
62
|
+
ALIGN[column.align ?? "left"],
|
|
63
|
+
column.headerClassName
|
|
64
|
+
),
|
|
65
|
+
children: column.title
|
|
66
|
+
},
|
|
67
|
+
column.key
|
|
68
|
+
)) })
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.length === 0 && emptyMessage != null ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
|
|
72
|
+
"td",
|
|
73
|
+
{
|
|
74
|
+
colSpan: columns.length,
|
|
75
|
+
className: "text-muted-foreground px-4 py-6 text-center text-sm",
|
|
76
|
+
children: emptyMessage
|
|
77
|
+
}
|
|
78
|
+
) }) : rows.map((row, index) => /* @__PURE__ */ jsx(
|
|
79
|
+
"tr",
|
|
80
|
+
{
|
|
81
|
+
className: cn(
|
|
82
|
+
"border-border/70 border-b last:border-0",
|
|
83
|
+
rowClassName?.(row, index)
|
|
84
|
+
),
|
|
85
|
+
children: columns.map((column) => /* @__PURE__ */ jsx(
|
|
86
|
+
"td",
|
|
87
|
+
{
|
|
88
|
+
className: cn(
|
|
89
|
+
DENSITY[density],
|
|
90
|
+
ALIGN[column.align ?? "left"],
|
|
91
|
+
column.className
|
|
92
|
+
),
|
|
93
|
+
children: column.cell(row, index)
|
|
94
|
+
},
|
|
95
|
+
column.key
|
|
96
|
+
))
|
|
97
|
+
},
|
|
98
|
+
rowKey(row, index)
|
|
99
|
+
)) }),
|
|
100
|
+
footer != null ? /* @__PURE__ */ jsx("tfoot", { children: footer }) : null
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
DataTable
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=chunk-ZLAA4DGG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data-table.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The list table every feature panel was hand-rolling.\n *\n * Seventeen files repeated the same wrapper string\n * (`border-border bg-background/80 overflow-x-auto rounded-lg border`) and the\n * header had already drifted into two incompatible looks, so Campaigns and\n * Targets did not resemble each other. Both are kept as `variant` rather than\n * unified here: collapsing them is a visual change to ten panels and belongs in\n * its own reviewable diff, not smuggled in with a refactor.\n *\n * The column contract deliberately echoes `GridColumn` in\n * `spreadsheet-grid.tsx` — `key`, `title`, a renderer, an optional class — so\n * the repo has one shape to learn. It drops that type's `value: (row) => string`\n * because there is no clipboard or CSV here; nothing would keep it honest, and\n * a required field nobody reads gets filled in with lies.\n *\n * **Not for every table.** `GroupTable` renders parent and child rows with\n * different column counts, and `BusinessTable`'s rows are dnd-kit sortables that\n * own their own refs and transforms. `PublishModal`, `BulkCreateModal` and the\n * clone queue in `MetaAdSetsPanel` are grids of inputs rather than lists of\n * values — every cell is an editable control, and they size their header and\n * body rows differently on purpose. All five are legitimately tables of a\n * different kind; bending this component around them would cost more than it\n * saves.\n */\nexport type TableColumn<Row> = {\n key: string;\n /** Node, not string: a header can carry a select-all box or a sort control. */\n title: React.ReactNode;\n cell: (row: Row, index: number) => React.ReactNode;\n align?: \"left\" | \"right\" | \"center\";\n /**\n * Set on a sortable column so a screen reader announces the current sort.\n * Only the sorted column says `ascending`/`descending`; the rest say `none`,\n * which is what marks them as sortable at all.\n */\n ariaSort?: \"ascending\" | \"descending\" | \"none\";\n /** Extra classes for this column's `<td>`. */\n className?: string;\n /** Extra classes for this column's `<th>`. */\n headerClassName?: string;\n};\n\nexport interface DataTableProps<Row> {\n columns: TableColumn<Row>[];\n rows: Row[];\n /** Stable React key per row. */\n rowKey: (row: Row, index: number) => string;\n /**\n * `uppercase` — the tinted small-caps header (campaigns, showcase, filters).\n * `plain` — the quieter left-aligned header (ads, targets, creatives).\n */\n variant?: \"uppercase\" | \"plain\";\n /**\n * `comfortable` — the roomy default, for tables of a handful of columns.\n * `compact` — what every table of eight columns or more had already chosen by\n * hand, because the roomy padding pushes them into a horizontal scroll.\n */\n density?: \"comfortable\" | \"compact\";\n /** Pins the header for a table inside its own scroll container. */\n stickyHeader?: boolean;\n rowClassName?: (row: Row, index: number) => string | undefined;\n /** Rendered in place of `<tbody>` rows when there are none. */\n emptyMessage?: React.ReactNode;\n /**\n * Rows for a `<tfoot>` — a totals line, typically. The caller supplies the\n * `<tr>` itself because a summary row rarely lines up with the columns above\n * it; designer-counts spans its first two.\n */\n footer?: React.ReactNode;\n /** Names the table for screen readers when no visible caption fits. */\n ariaLabel?: string;\n /** Classes for the scroll wrapper. */\n className?: string;\n /** Classes for the `<table>`. */\n tableClassName?: string;\n}\n\nconst HEADER_VARIANTS = {\n uppercase:\n \"border-border bg-muted/40 text-muted-foreground border-b text-xs tracking-wide uppercase\",\n plain: \"text-muted-foreground border-border border-b text-left\",\n} as const;\n\nconst DENSITY = {\n comfortable: \"px-4 py-3\",\n compact: \"px-3 py-2\",\n} as const;\n\nconst ALIGN = {\n left: \"text-left\",\n right: \"text-right\",\n center: \"text-center\",\n} as const;\n\nexport function DataTable<Row>({\n columns,\n rows,\n rowKey,\n variant = \"uppercase\",\n density = \"comfortable\",\n stickyHeader = false,\n rowClassName,\n emptyMessage,\n footer,\n ariaLabel,\n className,\n tableClassName,\n}: DataTableProps<Row>) {\n return (\n <div\n className={cn(\n \"border-border bg-background/80 overflow-x-auto rounded-lg border\",\n className,\n )}\n >\n <table\n aria-label={ariaLabel}\n className={cn(\"min-w-full text-left text-sm\", tableClassName)}\n >\n <thead\n className={cn(\n HEADER_VARIANTS[variant],\n stickyHeader && \"bg-muted sticky top-0 z-10\",\n )}\n >\n <tr>\n {columns.map((column) => (\n <th\n key={column.key}\n scope=\"col\"\n aria-sort={column.ariaSort}\n className={cn(\n DENSITY[density],\n \"font-medium\",\n ALIGN[column.align ?? \"left\"],\n column.headerClassName,\n )}\n >\n {column.title}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.length === 0 && emptyMessage != null ? (\n <tr>\n {/* One cell across the whole width — a short row would otherwise\n leave the message sitting under the first column. */}\n <td\n colSpan={columns.length}\n className=\"text-muted-foreground px-4 py-6 text-center text-sm\"\n >\n {emptyMessage}\n </td>\n </tr>\n ) : (\n rows.map((row, index) => (\n <tr\n key={rowKey(row, index)}\n className={cn(\n \"border-border/70 border-b last:border-0\",\n rowClassName?.(row, index),\n )}\n >\n {columns.map((column) => (\n <td\n key={column.key}\n className={cn(\n DENSITY[density],\n ALIGN[column.align ?? \"left\"],\n column.className,\n )}\n >\n {column.cell(row, index)}\n </td>\n ))}\n </tr>\n ))\n )}\n </tbody>\n {footer != null ? <tfoot>{footer}</tfoot> : null}\n </table>\n </div>\n );\n}\n"],"mappings":";;;;;AAwHM,SAYQ,KAZR;AAtCN,IAAM,kBAAkB;AAAA,EACtB,WACE;AAAA,EACF,OAAO;AACT;AAEA,IAAM,UAAU;AAAA,EACd,aAAa;AAAA,EACb,SAAS;AACX;AAEA,IAAM,QAAQ;AAAA,EACZ,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AACV;AAEO,SAAS,UAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,cAAY;AAAA,UACZ,WAAW,GAAG,gCAAgC,cAAc;AAAA,UAE5D;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAW;AAAA,kBACT,gBAAgB,OAAO;AAAA,kBACvB,gBAAgB;AAAA,gBAClB;AAAA,gBAEA,8BAAC,QACE,kBAAQ,IAAI,CAAC,WACZ;AAAA,kBAAC;AAAA;AAAA,oBAEC,OAAM;AAAA,oBACN,aAAW,OAAO;AAAA,oBAClB,WAAW;AAAA,sBACT,QAAQ,OAAO;AAAA,sBACf;AAAA,sBACA,MAAM,OAAO,SAAS,MAAM;AAAA,sBAC5B,OAAO;AAAA,oBACT;AAAA,oBAEC,iBAAO;AAAA;AAAA,kBAVH,OAAO;AAAA,gBAWd,CACD,GACH;AAAA;AAAA,YACF;AAAA,YACA,oBAAC,WACE,eAAK,WAAW,KAAK,gBAAgB,OACpC,oBAAC,QAGC;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,QAAQ;AAAA,gBACjB,WAAU;AAAA,gBAET;AAAA;AAAA,YACH,GACF,IAEA,KAAK,IAAI,CAAC,KAAK,UACb;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAW;AAAA,kBACT;AAAA,kBACA,eAAe,KAAK,KAAK;AAAA,gBAC3B;AAAA,gBAEC,kBAAQ,IAAI,CAAC,WACZ;AAAA,kBAAC;AAAA;AAAA,oBAEC,WAAW;AAAA,sBACT,QAAQ,OAAO;AAAA,sBACf,MAAM,OAAO,SAAS,MAAM;AAAA,sBAC5B,OAAO;AAAA,oBACT;AAAA,oBAEC,iBAAO,KAAK,KAAK,KAAK;AAAA;AAAA,kBAPlB,OAAO;AAAA,gBAQd,CACD;AAAA;AAAA,cAjBI,OAAO,KAAK,KAAK;AAAA,YAkBxB,CACD,GAEL;AAAA,YACC,UAAU,OAAO,oBAAC,WAAO,kBAAO,IAAW;AAAA;AAAA;AAAA,MAC9C;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-HCA5ODCD.js";
|
|
4
|
+
|
|
5
|
+
// src/alert.tsx
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { cva } from "class-variance-authority";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var alertVariants = cva("rounded-lg border px-3 py-2 text-sm", {
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
danger: "border-destructive/30 bg-destructive/5 text-destructive",
|
|
13
|
+
warning: "border-warning/30 bg-warning/5 text-warning",
|
|
14
|
+
success: "border-success/30 bg-success/5 text-success",
|
|
15
|
+
info: "border-info/30 bg-info/5 text-info",
|
|
16
|
+
/**
|
|
17
|
+
* The quiet state of a banner that also has loud ones — "no default
|
|
18
|
+
* campaign set" next to "this is the default". Without it those callers
|
|
19
|
+
* keep a ternary of raw class strings and the shared component only
|
|
20
|
+
* covers half of what they draw.
|
|
21
|
+
*/
|
|
22
|
+
neutral: "border-border bg-muted/40 text-muted-foreground"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
// Failure is what a banner almost always reports here, and defaulting to
|
|
27
|
+
// it means the noisy case cannot be forgotten.
|
|
28
|
+
variant: "danger"
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
var Alert = React.forwardRef(
|
|
32
|
+
({ className, variant, action, children, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
33
|
+
"div",
|
|
34
|
+
{
|
|
35
|
+
ref,
|
|
36
|
+
role: variant === "danger" || variant == null ? "alert" : "status",
|
|
37
|
+
className: cn(
|
|
38
|
+
alertVariants({ variant }),
|
|
39
|
+
action && "flex flex-wrap items-center justify-between gap-3",
|
|
40
|
+
className
|
|
41
|
+
),
|
|
42
|
+
...props,
|
|
43
|
+
children: [
|
|
44
|
+
action ? /* @__PURE__ */ jsx("span", { children }) : children,
|
|
45
|
+
action
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
Alert.displayName = "Alert";
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
alertVariants,
|
|
54
|
+
Alert
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=chunk-ZP252OJZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/alert.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The inline message banner — a failed load, a warning above a form, a note.\n *\n * This was the most duplicated markup in the panel: thirty blocks across\n * twenty-eight files, spelled seven different ways. `px-3 py-2` in most places\n * and `px-4 py-3` in others, `text-rose-700` here and `text-destructive`\n * there — all meaning \"something went wrong\".\n *\n * `role` is derived from the variant rather than left to callers. An error a\n * user needs to act on should interrupt a screen reader; a note should not.\n * Almost none of the hand-rolled copies set either.\n */\nconst alertVariants = cva(\"rounded-lg border px-3 py-2 text-sm\", {\n variants: {\n variant: {\n danger: \"border-destructive/30 bg-destructive/5 text-destructive\",\n warning: \"border-warning/30 bg-warning/5 text-warning\",\n success: \"border-success/30 bg-success/5 text-success\",\n info: \"border-info/30 bg-info/5 text-info\",\n /**\n * The quiet state of a banner that also has loud ones — \"no default\n * campaign set\" next to \"this is the default\". Without it those callers\n * keep a ternary of raw class strings and the shared component only\n * covers half of what they draw.\n */\n neutral: \"border-border bg-muted/40 text-muted-foreground\",\n },\n },\n defaultVariants: {\n // Failure is what a banner almost always reports here, and defaulting to\n // it means the noisy case cannot be forgotten.\n variant: \"danger\",\n },\n});\n\nexport interface AlertProps\n extends\n React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof alertVariants> {\n /**\n * A control belonging to the message, such as Retry. Given one, the banner\n * lays out as a row and keeps the action at its end.\n */\n action?: React.ReactNode;\n}\n\nexport const Alert = React.forwardRef<HTMLDivElement, AlertProps>(\n ({ className, variant, action, children, ...props }, ref) => (\n <div\n ref={ref}\n role={variant === \"danger\" || variant == null ? \"alert\" : \"status\"}\n className={cn(\n alertVariants({ variant }),\n action && \"flex flex-wrap items-center justify-between gap-3\",\n className,\n )}\n {...props}\n >\n {/* Wrapped so the action sits opposite the text rather than being\n pushed around by whatever the caller passed as children. */}\n {action ? <span>{children}</span> : children}\n {action}\n </div>\n ),\n);\n\nAlert.displayName = \"Alert\";\n\nexport { alertVariants };\n"],"mappings":";;;;;AAAA,YAAY,WAAW;AACvB,SAAS,WAA8B;AAoDnC,SAYY,KAZZ;AApCJ,IAAM,gBAAgB,IAAI,uCAAuC;AAAA,EAC/D,UAAU;AAAA,IACR,SAAS;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAON,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,iBAAiB;AAAA;AAAA;AAAA,IAGf,SAAS;AAAA,EACX;AACF,CAAC;AAaM,IAAM,QAAc;AAAA,EACzB,CAAC,EAAE,WAAW,SAAS,QAAQ,UAAU,GAAG,MAAM,GAAG,QACnD;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAM,YAAY,YAAY,WAAW,OAAO,UAAU;AAAA,MAC1D,WAAW;AAAA,QACT,cAAc,EAAE,QAAQ,CAAC;AAAA,QACzB,UAAU;AAAA,QACV;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAIH;AAAA,iBAAS,oBAAC,UAAM,UAAS,IAAU;AAAA,QACnC;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,MAAM,cAAc;","names":[]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/clipboard.ts
|
|
21
|
+
var clipboard_exports = {};
|
|
22
|
+
__export(clipboard_exports, {
|
|
23
|
+
CSV_DELIMITER: () => CSV_DELIMITER,
|
|
24
|
+
TSV_DELIMITER: () => TSV_DELIMITER,
|
|
25
|
+
buildRangeText: () => buildRangeText,
|
|
26
|
+
countCells: () => countCells,
|
|
27
|
+
describeRange: () => describeRange,
|
|
28
|
+
escapeCell: () => escapeCell,
|
|
29
|
+
fullRange: () => fullRange,
|
|
30
|
+
normalizeRange: () => normalizeRange,
|
|
31
|
+
writeToClipboard: () => writeToClipboard
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(clipboard_exports);
|
|
34
|
+
var TSV_DELIMITER = " ";
|
|
35
|
+
var CSV_DELIMITER = ",";
|
|
36
|
+
function normalizeRange(anchor, focus) {
|
|
37
|
+
if (!anchor || !focus) return null;
|
|
38
|
+
return {
|
|
39
|
+
r1: Math.min(anchor.r, focus.r),
|
|
40
|
+
r2: Math.max(anchor.r, focus.r),
|
|
41
|
+
c1: Math.min(anchor.c, focus.c),
|
|
42
|
+
c2: Math.max(anchor.c, focus.c)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function escapeCell(raw, delimiter) {
|
|
46
|
+
const value = raw == null ? "" : String(raw);
|
|
47
|
+
if (value.includes(delimiter) || value.includes("\n") || value.includes("\r") || value.includes('"')) {
|
|
48
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function buildRangeText(range, columns, rows, delimiter, includeHeaders) {
|
|
53
|
+
if (!range) return "";
|
|
54
|
+
const lines = [];
|
|
55
|
+
if (includeHeaders) {
|
|
56
|
+
const header = [];
|
|
57
|
+
for (let c = range.c1; c <= range.c2; c++) {
|
|
58
|
+
header.push(escapeCell(columns[c]?.title, delimiter));
|
|
59
|
+
}
|
|
60
|
+
lines.push(header.join(delimiter));
|
|
61
|
+
}
|
|
62
|
+
for (let r = range.r1; r <= range.r2; r++) {
|
|
63
|
+
const line = [];
|
|
64
|
+
const row = rows[r];
|
|
65
|
+
for (let c = range.c1; c <= range.c2; c++) {
|
|
66
|
+
const column = columns[c];
|
|
67
|
+
line.push(
|
|
68
|
+
escapeCell(
|
|
69
|
+
column && row !== void 0 ? column.value(row) : "",
|
|
70
|
+
delimiter
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
lines.push(line.join(delimiter));
|
|
75
|
+
}
|
|
76
|
+
return lines.join("\n");
|
|
77
|
+
}
|
|
78
|
+
function fullRange(columns, rows) {
|
|
79
|
+
if (!rows.length || !columns.length) return null;
|
|
80
|
+
return { r1: 0, r2: rows.length - 1, c1: 0, c2: columns.length - 1 };
|
|
81
|
+
}
|
|
82
|
+
function describeRange(range) {
|
|
83
|
+
if (!range) return "No selection";
|
|
84
|
+
const rowCount = range.r2 - range.r1 + 1;
|
|
85
|
+
const colCount = range.c2 - range.c1 + 1;
|
|
86
|
+
if (rowCount === 1 && colCount === 1) return "1 cell selected";
|
|
87
|
+
return `${rowCount} \xD7 ${colCount} selected`;
|
|
88
|
+
}
|
|
89
|
+
function countCells(range) {
|
|
90
|
+
return {
|
|
91
|
+
rows: range.r2 - range.r1 + 1,
|
|
92
|
+
columns: range.c2 - range.c1 + 1
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function writeToClipboard(text) {
|
|
96
|
+
if (navigator.clipboard?.writeText) {
|
|
97
|
+
return navigator.clipboard.writeText(text);
|
|
98
|
+
}
|
|
99
|
+
const textarea = document.createElement("textarea");
|
|
100
|
+
textarea.value = text;
|
|
101
|
+
textarea.style.position = "fixed";
|
|
102
|
+
textarea.style.top = "-1000px";
|
|
103
|
+
document.body.appendChild(textarea);
|
|
104
|
+
textarea.select();
|
|
105
|
+
try {
|
|
106
|
+
if (!document.execCommand("copy")) {
|
|
107
|
+
throw new Error("Could not write to the clipboard");
|
|
108
|
+
}
|
|
109
|
+
} finally {
|
|
110
|
+
document.body.removeChild(textarea);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
114
|
+
0 && (module.exports = {
|
|
115
|
+
CSV_DELIMITER,
|
|
116
|
+
TSV_DELIMITER,
|
|
117
|
+
buildRangeText,
|
|
118
|
+
countCells,
|
|
119
|
+
describeRange,
|
|
120
|
+
escapeCell,
|
|
121
|
+
fullRange,
|
|
122
|
+
normalizeRange,
|
|
123
|
+
writeToClipboard
|
|
124
|
+
});
|
|
125
|
+
//# sourceMappingURL=clipboard.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/clipboard.ts"],"sourcesContent":["/**\n * Range maths and clipboard text for the card table's spreadsheet selection.\n *\n * Ported from video-shorts-panel `app/src/common/components/ExcelTable.jsx`.\n * Kept free of React and the DOM (except `writeToClipboard`) so the escaping\n * and range rules are unit-testable.\n */\n\nexport type CellRef = { r: number; c: number };\n\nexport type CellRange = { r1: number; r2: number; c1: number; c2: number };\n\n/** Tab for the clipboard (Excel/Sheets split on it), comma for a .csv file. */\nexport const TSV_DELIMITER = \"\\t\";\nexport const CSV_DELIMITER = \",\";\n\n/**\n * Anchor and focus can be dragged in any direction, so the range is whichever\n * corners they happen to be — normalize to top-left / bottom-right.\n */\nexport function normalizeRange(\n anchor: CellRef | null | undefined,\n focus: CellRef | null | undefined,\n): CellRange | null {\n if (!anchor || !focus) return null;\n return {\n r1: Math.min(anchor.r, focus.r),\n r2: Math.max(anchor.r, focus.r),\n c1: Math.min(anchor.c, focus.c),\n c2: Math.max(anchor.c, focus.c),\n };\n}\n\n/**\n * A cell needs quoting once it carries the delimiters themselves — the same\n * rule Excel uses when it parses the clipboard. Without it, a title containing\n * a tab or a newline silently becomes extra columns or rows on paste.\n */\nexport function escapeCell(raw: unknown, delimiter: string): string {\n const value = raw == null ? \"\" : String(raw);\n if (\n value.includes(delimiter) ||\n value.includes(\"\\n\") ||\n value.includes(\"\\r\") ||\n value.includes('\"')\n ) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\nexport type TextColumn<Row> = {\n title: string;\n value: (row: Row) => string;\n};\n\n/** Renders a range as delimited text, optionally with a header line. */\nexport function buildRangeText<Row>(\n range: CellRange | null,\n columns: TextColumn<Row>[],\n rows: Row[],\n delimiter: string,\n includeHeaders: boolean,\n): string {\n if (!range) return \"\";\n const lines: string[] = [];\n\n if (includeHeaders) {\n const header: string[] = [];\n for (let c = range.c1; c <= range.c2; c++) {\n header.push(escapeCell(columns[c]?.title, delimiter));\n }\n lines.push(header.join(delimiter));\n }\n\n for (let r = range.r1; r <= range.r2; r++) {\n const line: string[] = [];\n const row = rows[r];\n for (let c = range.c1; c <= range.c2; c++) {\n const column = columns[c];\n line.push(\n escapeCell(\n column && row !== undefined ? column.value(row) : \"\",\n delimiter,\n ),\n );\n }\n lines.push(line.join(delimiter));\n }\n\n return lines.join(\"\\n\");\n}\n\n/** The whole grid, or null when there is nothing to select. */\nexport function fullRange<Row>(\n columns: TextColumn<Row>[],\n rows: Row[],\n): CellRange | null {\n if (!rows.length || !columns.length) return null;\n return { r1: 0, r2: rows.length - 1, c1: 0, c2: columns.length - 1 };\n}\n\n/** \"3 × 2 selected\" — what the toolbar reports back to the user. */\nexport function describeRange(range: CellRange | null): string {\n if (!range) return \"No selection\";\n const rowCount = range.r2 - range.r1 + 1;\n const colCount = range.c2 - range.c1 + 1;\n if (rowCount === 1 && colCount === 1) return \"1 cell selected\";\n return `${rowCount} × ${colCount} selected`;\n}\n\nexport function countCells(range: CellRange): {\n rows: number;\n columns: number;\n} {\n return {\n rows: range.r2 - range.r1 + 1,\n columns: range.c2 - range.c1 + 1,\n };\n}\n\n/**\n * `navigator.clipboard` is unavailable on http origins and older browsers, so\n * fall back to a hidden textarea + execCommand rather than failing the copy.\n */\nexport async function writeToClipboard(text: string): Promise<void> {\n if (navigator.clipboard?.writeText) {\n return navigator.clipboard.writeText(text);\n }\n\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.top = \"-1000px\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n if (!document.execCommand(\"copy\")) {\n throw new Error(\"Could not write to the clipboard\");\n }\n } finally {\n document.body.removeChild(textarea);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Range maths and clipboard text for the card table's spreadsheet selection.
|
|
3
|
+
*
|
|
4
|
+
* Ported from video-shorts-panel `app/src/common/components/ExcelTable.jsx`.
|
|
5
|
+
* Kept free of React and the DOM (except `writeToClipboard`) so the escaping
|
|
6
|
+
* and range rules are unit-testable.
|
|
7
|
+
*/
|
|
8
|
+
type CellRef = {
|
|
9
|
+
r: number;
|
|
10
|
+
c: number;
|
|
11
|
+
};
|
|
12
|
+
type CellRange = {
|
|
13
|
+
r1: number;
|
|
14
|
+
r2: number;
|
|
15
|
+
c1: number;
|
|
16
|
+
c2: number;
|
|
17
|
+
};
|
|
18
|
+
/** Tab for the clipboard (Excel/Sheets split on it), comma for a .csv file. */
|
|
19
|
+
declare const TSV_DELIMITER = "\t";
|
|
20
|
+
declare const CSV_DELIMITER = ",";
|
|
21
|
+
/**
|
|
22
|
+
* Anchor and focus can be dragged in any direction, so the range is whichever
|
|
23
|
+
* corners they happen to be — normalize to top-left / bottom-right.
|
|
24
|
+
*/
|
|
25
|
+
declare function normalizeRange(anchor: CellRef | null | undefined, focus: CellRef | null | undefined): CellRange | null;
|
|
26
|
+
/**
|
|
27
|
+
* A cell needs quoting once it carries the delimiters themselves — the same
|
|
28
|
+
* rule Excel uses when it parses the clipboard. Without it, a title containing
|
|
29
|
+
* a tab or a newline silently becomes extra columns or rows on paste.
|
|
30
|
+
*/
|
|
31
|
+
declare function escapeCell(raw: unknown, delimiter: string): string;
|
|
32
|
+
type TextColumn<Row> = {
|
|
33
|
+
title: string;
|
|
34
|
+
value: (row: Row) => string;
|
|
35
|
+
};
|
|
36
|
+
/** Renders a range as delimited text, optionally with a header line. */
|
|
37
|
+
declare function buildRangeText<Row>(range: CellRange | null, columns: TextColumn<Row>[], rows: Row[], delimiter: string, includeHeaders: boolean): string;
|
|
38
|
+
/** The whole grid, or null when there is nothing to select. */
|
|
39
|
+
declare function fullRange<Row>(columns: TextColumn<Row>[], rows: Row[]): CellRange | null;
|
|
40
|
+
/** "3 × 2 selected" — what the toolbar reports back to the user. */
|
|
41
|
+
declare function describeRange(range: CellRange | null): string;
|
|
42
|
+
declare function countCells(range: CellRange): {
|
|
43
|
+
rows: number;
|
|
44
|
+
columns: number;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* `navigator.clipboard` is unavailable on http origins and older browsers, so
|
|
48
|
+
* fall back to a hidden textarea + execCommand rather than failing the copy.
|
|
49
|
+
*/
|
|
50
|
+
declare function writeToClipboard(text: string): Promise<void>;
|
|
51
|
+
|
|
52
|
+
export { CSV_DELIMITER, type CellRange, type CellRef, TSV_DELIMITER, type TextColumn, buildRangeText, countCells, describeRange, escapeCell, fullRange, normalizeRange, writeToClipboard };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Range maths and clipboard text for the card table's spreadsheet selection.
|
|
3
|
+
*
|
|
4
|
+
* Ported from video-shorts-panel `app/src/common/components/ExcelTable.jsx`.
|
|
5
|
+
* Kept free of React and the DOM (except `writeToClipboard`) so the escaping
|
|
6
|
+
* and range rules are unit-testable.
|
|
7
|
+
*/
|
|
8
|
+
type CellRef = {
|
|
9
|
+
r: number;
|
|
10
|
+
c: number;
|
|
11
|
+
};
|
|
12
|
+
type CellRange = {
|
|
13
|
+
r1: number;
|
|
14
|
+
r2: number;
|
|
15
|
+
c1: number;
|
|
16
|
+
c2: number;
|
|
17
|
+
};
|
|
18
|
+
/** Tab for the clipboard (Excel/Sheets split on it), comma for a .csv file. */
|
|
19
|
+
declare const TSV_DELIMITER = "\t";
|
|
20
|
+
declare const CSV_DELIMITER = ",";
|
|
21
|
+
/**
|
|
22
|
+
* Anchor and focus can be dragged in any direction, so the range is whichever
|
|
23
|
+
* corners they happen to be — normalize to top-left / bottom-right.
|
|
24
|
+
*/
|
|
25
|
+
declare function normalizeRange(anchor: CellRef | null | undefined, focus: CellRef | null | undefined): CellRange | null;
|
|
26
|
+
/**
|
|
27
|
+
* A cell needs quoting once it carries the delimiters themselves — the same
|
|
28
|
+
* rule Excel uses when it parses the clipboard. Without it, a title containing
|
|
29
|
+
* a tab or a newline silently becomes extra columns or rows on paste.
|
|
30
|
+
*/
|
|
31
|
+
declare function escapeCell(raw: unknown, delimiter: string): string;
|
|
32
|
+
type TextColumn<Row> = {
|
|
33
|
+
title: string;
|
|
34
|
+
value: (row: Row) => string;
|
|
35
|
+
};
|
|
36
|
+
/** Renders a range as delimited text, optionally with a header line. */
|
|
37
|
+
declare function buildRangeText<Row>(range: CellRange | null, columns: TextColumn<Row>[], rows: Row[], delimiter: string, includeHeaders: boolean): string;
|
|
38
|
+
/** The whole grid, or null when there is nothing to select. */
|
|
39
|
+
declare function fullRange<Row>(columns: TextColumn<Row>[], rows: Row[]): CellRange | null;
|
|
40
|
+
/** "3 × 2 selected" — what the toolbar reports back to the user. */
|
|
41
|
+
declare function describeRange(range: CellRange | null): string;
|
|
42
|
+
declare function countCells(range: CellRange): {
|
|
43
|
+
rows: number;
|
|
44
|
+
columns: number;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* `navigator.clipboard` is unavailable on http origins and older browsers, so
|
|
48
|
+
* fall back to a hidden textarea + execCommand rather than failing the copy.
|
|
49
|
+
*/
|
|
50
|
+
declare function writeToClipboard(text: string): Promise<void>;
|
|
51
|
+
|
|
52
|
+
export { CSV_DELIMITER, type CellRange, type CellRef, TSV_DELIMITER, type TextColumn, buildRangeText, countCells, describeRange, escapeCell, fullRange, normalizeRange, writeToClipboard };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CSV_DELIMITER,
|
|
3
|
+
TSV_DELIMITER,
|
|
4
|
+
buildRangeText,
|
|
5
|
+
countCells,
|
|
6
|
+
describeRange,
|
|
7
|
+
escapeCell,
|
|
8
|
+
fullRange,
|
|
9
|
+
normalizeRange,
|
|
10
|
+
writeToClipboard
|
|
11
|
+
} from "./chunk-Y5WCT6XN.js";
|
|
12
|
+
export {
|
|
13
|
+
CSV_DELIMITER,
|
|
14
|
+
TSV_DELIMITER,
|
|
15
|
+
buildRangeText,
|
|
16
|
+
countCells,
|
|
17
|
+
describeRange,
|
|
18
|
+
escapeCell,
|
|
19
|
+
fullRange,
|
|
20
|
+
normalizeRange,
|
|
21
|
+
writeToClipboard
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=clipboard.js.map
|