@crisp-ui-kit/crisp 0.34.0 → 0.36.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/dist/bulkbar.css +3 -0
- package/dist/bulkbar.layered.css +3 -0
- package/dist/cjs/components/bulkbar/bulkbar.js +1 -1
- package/dist/cjs/components/bulkbar/bulkbar.recipe.js +6 -0
- package/dist/cjs/components/datatable/datatable.js +65 -5
- package/dist/cjs/components/datatable/datatable.recipe.js +2 -0
- package/dist/cjs/components/filldoc/filldoc.js +102 -0
- package/dist/cjs/components/filldoc/filldoc.recipe.js +211 -0
- package/dist/cjs/components/filldoc/index.js +10 -0
- package/dist/cjs/components/filldoc/serialize.js +39 -0
- package/dist/cjs/components/fillsheet/fillsheet.js +719 -0
- package/dist/cjs/components/fillsheet/fillsheet.recipe.js +325 -0
- package/dist/cjs/components/fillsheet/index.js +12 -0
- package/dist/cjs/components/fillsheet/serialize.js +82 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/product/recordpanel/recordpanel.js +4 -2
- package/dist/cjs/product/recordpanel/recordpanel.recipe.js +8 -0
- package/dist/cjs/product/sidebar/sidebar.js +16 -3
- package/dist/datatable.css +13 -5
- package/dist/datatable.layered.css +13 -5
- package/dist/esm/components/bulkbar/bulkbar.js +1 -1
- package/dist/esm/components/bulkbar/bulkbar.recipe.js +6 -0
- package/dist/esm/components/datatable/datatable.js +65 -5
- package/dist/esm/components/datatable/datatable.recipe.js +2 -0
- package/dist/esm/components/filldoc/filldoc.js +66 -0
- package/dist/esm/components/filldoc/filldoc.recipe.js +208 -0
- package/dist/esm/components/filldoc/index.js +3 -0
- package/dist/esm/components/filldoc/serialize.js +35 -0
- package/dist/esm/components/fillsheet/fillsheet.js +683 -0
- package/dist/esm/components/fillsheet/fillsheet.recipe.js +322 -0
- package/dist/esm/components/fillsheet/index.js +3 -0
- package/dist/esm/components/fillsheet/serialize.js +76 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/product/recordpanel/recordpanel.js +4 -2
- package/dist/esm/product/recordpanel/recordpanel.recipe.js +8 -0
- package/dist/esm/product/sidebar/sidebar.js +17 -4
- package/dist/filldoc.css +31 -0
- package/dist/filldoc.d.ts +7 -0
- package/dist/filldoc.layered.css +33 -0
- package/dist/fillsheet.css +29 -0
- package/dist/fillsheet.d.ts +7 -0
- package/dist/fillsheet.layered.css +31 -0
- package/dist/recordpanel.css +6 -0
- package/dist/recordpanel.layered.css +6 -0
- package/dist/styles.css +84 -5
- package/dist/styles.layered.css +84 -5
- package/dist/types/components/datatable/datatable.d.ts +7 -1
- package/dist/types/components/filldoc/filldoc.d.ts +79 -0
- package/dist/types/components/filldoc/filldoc.recipe.d.ts +201 -0
- package/dist/types/components/filldoc/index.d.ts +3 -0
- package/dist/types/components/filldoc/serialize.d.ts +5 -0
- package/dist/types/components/fillsheet/fillsheet.d.ts +165 -0
- package/dist/types/components/fillsheet/fillsheet.recipe.d.ts +202 -0
- package/dist/types/components/fillsheet/index.d.ts +3 -0
- package/dist/types/components/fillsheet/serialize.d.ts +45 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/product/recordpanel/recordpanel.d.ts +11 -1
- package/dist/types/product/sidebar/sidebar.d.ts +56 -0
- package/package.json +11 -1
package/dist/bulkbar.css
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
& .crisp-bulkbar-count { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
|
|
6
6
|
& .crisp-bulkbar-count:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); color: var(--crisp-fg-primary); }
|
|
7
7
|
& .crisp-bulkbar-count svg { flex-shrink: 0; }
|
|
8
|
+
/* the ", Clear selection" ARIA concatenates onto the pill's name (#324)
|
|
9
|
+
-- read, never drawn. Same technique as Slider's thumblabel. */
|
|
10
|
+
& .crisp-bulkbar-clearlabel { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
8
11
|
& .crisp-bulkbar-btn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
|
|
9
12
|
& .crisp-bulkbar-btn:hover { background: var(--crisp-bg-raised); }
|
|
10
13
|
& .crisp-bulkbar-btn--danger { color: var(--crisp-bg-danger); }
|
package/dist/bulkbar.layered.css
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
& .crisp-bulkbar-count { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
|
|
7
7
|
& .crisp-bulkbar-count:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); color: var(--crisp-fg-primary); }
|
|
8
8
|
& .crisp-bulkbar-count svg { flex-shrink: 0; }
|
|
9
|
+
/* the ", Clear selection" ARIA concatenates onto the pill's name (#324)
|
|
10
|
+
-- read, never drawn. Same technique as Slider's thumblabel. */
|
|
11
|
+
& .crisp-bulkbar-clearlabel { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
9
12
|
& .crisp-bulkbar-btn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
|
|
10
13
|
& .crisp-bulkbar-btn:hover { background: var(--crisp-bg-raised); }
|
|
11
14
|
& .crisp-bulkbar-btn--danger { color: var(--crisp-bg-danger); }
|
|
@@ -45,7 +45,7 @@ const bulkbar_recipe_1 = require("./bulkbar.recipe");
|
|
|
45
45
|
* (e.g. `count > 0 && <BulkBar …/>`). Measured from a records table.
|
|
46
46
|
*/
|
|
47
47
|
function BulkBar({ count, onClear, countLabel = "selected", countText = (count, label) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [count, " ", label] })), children, className, clearLabel = "Clear selection", ...rest }) {
|
|
48
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: (0, bulkbar_recipe_1.bulkbarRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-bulkbar-bar", children: [count != null && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-bulkbar-count", onClick: onClear, title: clearLabel, children: [countText(count, countLabel), (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13, strokeWidth: 2, "aria-hidden": "true" })] })), children] }) }));
|
|
48
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, bulkbar_recipe_1.bulkbarRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-bulkbar-bar", children: [count != null && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-bulkbar-count", onClick: onClear, title: clearLabel, children: [countText(count, countLabel), (0, jsx_runtime_1.jsx)("span", { className: "crisp-bulkbar-clearlabel", children: clearLabel }), (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13, strokeWidth: 2, "aria-hidden": "true" })] })), children] }) }));
|
|
49
49
|
}
|
|
50
50
|
BulkBar.displayName = "BulkBar";
|
|
51
51
|
/** A styled action button for use inside a `<BulkBar>` (or as a `<Menu>` trigger). */
|
|
@@ -18,6 +18,12 @@ const config = (0, core_1.defineRecipe)({
|
|
|
18
18
|
& .crisp-bulkbar-count { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; height: ${tokens_1.contract.size.menuRow}; margin: 0; padding: 0 ${tokens_1.contract.space["2"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, transparent); font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; white-space: nowrap; cursor: pointer; transition: background-color .12s; }
|
|
19
19
|
& .crisp-bulkbar-count:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 8%, transparent); color: ${tokens_1.contract.fgPrimary}; }
|
|
20
20
|
& .crisp-bulkbar-count svg { flex-shrink: 0; }
|
|
21
|
+
/* the ", Clear selection" ARIA concatenates onto the pill's name (#324)
|
|
22
|
+
-- read, never drawn. Same technique as Slider's thumblabel. */
|
|
23
|
+
& .crisp-bulkbar-clearlabel {
|
|
24
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
25
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
26
|
+
}
|
|
21
27
|
& .crisp-bulkbar-btn { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; height: ${tokens_1.contract.size.menuRow}; padding: 0 ${tokens_1.contract.space["2"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: transparent; color: ${tokens_1.contract.fgPrimary}; font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; white-space: nowrap; cursor: pointer; transition: background-color .12s; }
|
|
22
28
|
& .crisp-bulkbar-btn:hover { background: ${tokens_1.contract.bgRaised}; }
|
|
23
29
|
& .crisp-bulkbar-btn--danger { color: ${tokens_1.contract.bgDanger}; }
|
|
@@ -171,7 +171,7 @@ function renderTyped(col, value, avatarTone, display, locale = "en-US") {
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
const CHECK_W = 40;
|
|
174
|
-
function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel = "Select all rows", selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement = (count) => count === 0 ? "Selection cleared" : `${count} selected`, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder = "Search…", searchLabel = "Search table", onEdit, stickyFirst = true, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel = "New record", empty = "No results.", showCount = true, countLabel = (shown, total) => shown === total
|
|
174
|
+
function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel = "Select all rows", selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement = (count) => count === 0 ? "Selection cleared" : `${count} selected`, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder = "Search…", searchLabel = "Search table", onEdit, stickyFirst = true, stickyLast = false, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel = "New record", empty = "No results.", showCount = true, countLabel = (shown, total) => shown === total
|
|
175
175
|
? `${total} ${total === 1 ? "row" : "rows"}`
|
|
176
176
|
: `${shown} of ${total}`, toolbar, className, reorderRowLabel = "Reorder row", ...rest }) {
|
|
177
177
|
// Two different defaults on purpose. FORMATTING falls back to "en-US", which
|
|
@@ -271,6 +271,21 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
271
271
|
}
|
|
272
272
|
const [editing, setEditing] = React.useState(null);
|
|
273
273
|
const [scrolledX, setScrolledX] = React.useState(false);
|
|
274
|
+
// Mirrors `scrolledX` for the trailing edge: true while there is still
|
|
275
|
+
// unrevealed content sliding under the sticky trailing column, i.e. while
|
|
276
|
+
// NOT scrolled all the way to the end. Recomputed after every render (see
|
|
277
|
+
// the effect below), not just on scroll, because a column resize or a data
|
|
278
|
+
// change can move `scrollWidth` without firing a scroll event.
|
|
279
|
+
const [scrolledXEnd, setScrolledXEnd] = React.useState(false);
|
|
280
|
+
const scrollRef = React.useRef(null);
|
|
281
|
+
React.useEffect(() => {
|
|
282
|
+
if (!stickyLast)
|
|
283
|
+
return;
|
|
284
|
+
const el = scrollRef.current;
|
|
285
|
+
if (!el)
|
|
286
|
+
return;
|
|
287
|
+
setScrolledXEnd(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
288
|
+
});
|
|
274
289
|
// column widths (drag-to-resize) + fixed-layout sizing
|
|
275
290
|
const [widths, setWidths] = React.useState({});
|
|
276
291
|
const [resizingKey, setResizingKey] = React.useState(null);
|
|
@@ -469,6 +484,43 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
469
484
|
};
|
|
470
485
|
return {};
|
|
471
486
|
};
|
|
487
|
+
// Mirror of the group above, for a single trailing column (#327): the last
|
|
488
|
+
// VISIBLE data column (`columns`, not `allColumns` — a hidden column can't
|
|
489
|
+
// be the one that pins). `insetInlineEnd`, never `right`, for the same
|
|
490
|
+
// direction-safety reason `stickyCol` uses `insetInlineStart`: a physical
|
|
491
|
+
// `right` pins to the trailing edge only in LTR and swaps to the LEADING
|
|
492
|
+
// edge under `dir="rtl"`, which is backwards for a column meant to stay
|
|
493
|
+
// pinned to the reading direction's end.
|
|
494
|
+
const stickyLastCol = (index) => {
|
|
495
|
+
if (!stickyLast || index !== columns.length - 1)
|
|
496
|
+
return {};
|
|
497
|
+
return {
|
|
498
|
+
"data-sticky": "",
|
|
499
|
+
"data-sticky-edge-end": "",
|
|
500
|
+
style: { insetInlineEnd: 0 },
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
// Merges the two groups for a data-column cell. A column can be pinned by
|
|
504
|
+
// both (a one-column table with both flags on) — the header sticky outranks
|
|
505
|
+
// either via its own z-index rule (#285's ordering still holds), and here
|
|
506
|
+
// the two style objects merge rather than one overwriting the other.
|
|
507
|
+
const dataStickyCol = (index) => {
|
|
508
|
+
const lead = stickyCol(index);
|
|
509
|
+
const trail = stickyLastCol(index);
|
|
510
|
+
// Not `!lead["data-sticky"]`: the attribute's value is `""`, which is
|
|
511
|
+
// falsy, so that check silently treated every pinned column as unpinned.
|
|
512
|
+
if (!("data-sticky" in lead) && !("data-sticky" in trail))
|
|
513
|
+
return {};
|
|
514
|
+
return {
|
|
515
|
+
...lead,
|
|
516
|
+
...trail,
|
|
517
|
+
"data-sticky": "",
|
|
518
|
+
style: {
|
|
519
|
+
...lead.style,
|
|
520
|
+
...trail.style,
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
};
|
|
472
524
|
const alignFor = (col) => col.align ??
|
|
473
525
|
(col.type === "number" || col.type === "currency" ? "right" : undefined);
|
|
474
526
|
function startResize(col, e) {
|
|
@@ -506,8 +558,16 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
506
558
|
return ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 }));
|
|
507
559
|
}
|
|
508
560
|
return ((0, jsx_runtime_1.jsxs)("div", { ref: gridRef, className: (0, datatable_recipe_1.datatableRecipe)({ className }), tabIndex: -1, onKeyDown: onGridKeyDown, ...rest, children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-live", role: "status", "aria-live": "polite", children: selectionText }), (filterable || showCount || toolbar || columnMenu) && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-datatable-toolbar", children: [columnMenu && ((0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "start", trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datatable-viewsettings", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14, strokeWidth: 1.75 }), "View settings"] }), children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-group-label", children: "Columns" }), allColumns.map((c) => ((0, jsx_runtime_1.jsxs)(menu_1.MenuItem, { onSelect: () => c.hidden ? showColumn(c.key) : hideColumn(c.key), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 14, strokeWidth: 2, style: { opacity: c.hidden ? 0 : 1 } }), typeof c.header === "string" ? c.header : c.key] }, c.key)))] })), filterable && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("input", { type: "search", placeholder: searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": searchLabel })] })), showCount && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-count", children: countLabel(rows.length, data.length) })), toolbar] })), (0, jsx_runtime_1.jsxs)(table_1.Table, { interactive: interactive, surface: surface, style: { minWidth }, scrollProps: {
|
|
509
|
-
|
|
561
|
+
ref: scrollRef,
|
|
562
|
+
onScroll: (e) => {
|
|
563
|
+
const el = e.currentTarget;
|
|
564
|
+
setScrolledX(el.scrollLeft > 0);
|
|
565
|
+
if (stickyLast) {
|
|
566
|
+
setScrolledXEnd(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
567
|
+
}
|
|
568
|
+
},
|
|
510
569
|
...(scrolledX ? { "data-scrolled-x": "" } : {}),
|
|
570
|
+
...(scrolledXEnd ? { "data-scrolled-x-end": "" } : {}),
|
|
511
571
|
}, children: [(0, jsx_runtime_1.jsxs)("colgroup", { children: [showGrip && (0, jsx_runtime_1.jsx)("col", { style: { width: GRIP_W } }), selectable && (0, jsx_runtime_1.jsx)("col", { style: { width: CHECK_W } }), columns.map((col) => ((0, jsx_runtime_1.jsx)("col", { style: { width: colWidth(col) } }, col.key)))] }), (0, jsx_runtime_1.jsx)(table_1.TableHeader, { children: (0, jsx_runtime_1.jsxs)(table_1.TableRow, { children: [showGrip && ((0, jsx_runtime_1.jsx)(table_1.TableHead, { className: "crisp-datatable-col-grip", ...stickyCol(-2) })), selectable && ((0, jsx_runtime_1.jsx)(table_1.TableHead, { className: "crisp-datatable-col-check", ...stickyCol(-1), children: (0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-check", children: (0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { "aria-label": selectAllLabel, checked: allSelected, indeterminate: someSelected, onChange: toggleAll }) }) })), columns.map((col, i) => {
|
|
512
572
|
const Icon = col.icon;
|
|
513
573
|
// The sort direction was painted (`data-sorted`) and never
|
|
@@ -525,7 +585,7 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
525
585
|
: "descending"
|
|
526
586
|
: "none";
|
|
527
587
|
const inner = ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-th-inner", children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 14, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-th-label", children: col.header }), col.sortable ? (0, jsx_runtime_1.jsx)(ChevronFor, { colKey: col.key }) : null] }));
|
|
528
|
-
return ((0, jsx_runtime_1.jsxs)(table_1.TableHead, { align: alignFor(col), "aria-sort": ariaSort, ...
|
|
588
|
+
return ((0, jsx_runtime_1.jsxs)(table_1.TableHead, { align: alignFor(col), "aria-sort": ariaSort, ...dataStickyCol(i), children: [renaming === col.key ? ((0, jsx_runtime_1.jsx)("input", {
|
|
529
589
|
// biome-ignore lint/a11y/noAutofocus: the rename item opens this field
|
|
530
590
|
autoFocus: true, className: "crisp-datatable-th-rename", defaultValue: typeof col.header === "string" ? col.header : "", "aria-label": `Rename column`, onBlur: (e) => {
|
|
531
591
|
renameColumn(col.key, e.currentTarget.value);
|
|
@@ -565,7 +625,7 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
565
625
|
}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }) })), selectable && ((0, jsx_runtime_1.jsx)(table_1.TableCell, { className: "crisp-datatable-col-check", ...stickyCol(-1), onClick: (e) => e.stopPropagation(), children: (0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-check", children: (0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { "aria-label": selectRowLabel(row), checked: selected.has(row.id), onChange: () => toggleRow(row.id) }) }) })), columns.map((col, i) => {
|
|
566
626
|
const isEditing = editing?.id === row.id && editing?.key === col.key;
|
|
567
627
|
const value = cellValue(col, row);
|
|
568
|
-
const sticky =
|
|
628
|
+
const sticky = dataStickyCol(i);
|
|
569
629
|
if (isEditing) {
|
|
570
630
|
return ((0, jsx_runtime_1.jsx)(table_1.TableCell, { align: alignFor(col), ...sticky, onClick: (e) => e.stopPropagation(), children: (0, jsx_runtime_1.jsx)("input", { className: "crisp-datatable-edit",
|
|
571
631
|
// biome-ignore lint/a11y/noAutofocus: focus the field the moment it opens
|
|
@@ -598,6 +658,6 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
598
658
|
}, children: col.render
|
|
599
659
|
? col.render(value, row)
|
|
600
660
|
: renderTyped(col, value, avatarTone, col.format?.(value, row), locale) }, col.key));
|
|
601
|
-
})] }, row.id)))] }, group ?? "__ungrouped")))) }), (hasSummary || onAddRow) && ((0, jsx_runtime_1.jsxs)(table_1.TableFooter, { children: [hasSummary && ((0, jsx_runtime_1.jsxs)(table_1.TableRow, { children: [showGrip && (0, jsx_runtime_1.jsx)(table_1.TableCell, { ...stickyCol(-2) }), selectable && (0, jsx_runtime_1.jsx)(table_1.TableCell, { ...stickyCol(-1) }), columns.map((col, i) => ((0, jsx_runtime_1.jsx)(table_1.TableCell, { align: alignFor(col), ...
|
|
661
|
+
})] }, row.id)))] }, group ?? "__ungrouped")))) }), (hasSummary || onAddRow) && ((0, jsx_runtime_1.jsxs)(table_1.TableFooter, { children: [hasSummary && ((0, jsx_runtime_1.jsxs)(table_1.TableRow, { children: [showGrip && (0, jsx_runtime_1.jsx)(table_1.TableCell, { ...stickyCol(-2) }), selectable && (0, jsx_runtime_1.jsx)(table_1.TableCell, { ...stickyCol(-1) }), columns.map((col, i) => ((0, jsx_runtime_1.jsx)(table_1.TableCell, { align: alignFor(col), ...dataStickyCol(i), children: col.summary ? col.summary(rows) : null }, col.key)))] })), onAddRow && ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { className: "crisp-datatable-addcell", colSpan: totalCols, children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datatable-addrow", onClick: onAddRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), addRowLabel] }) }) }))] }))] })] }));
|
|
602
662
|
}
|
|
603
663
|
DataTable.displayName = "DataTable";
|
|
@@ -62,6 +62,8 @@ const config = (0, core_1.defineRecipe)({
|
|
|
62
62
|
& tbody tr[data-selected] td[data-sticky] { background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 10%, ${tokens_1.contract.bgSurface}); }
|
|
63
63
|
& tbody tr:hover td[data-sticky] { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, ${tokens_1.contract.bgSurface}); }
|
|
64
64
|
& .crisp-table-scroll[data-scrolled-x] [data-sticky-edge] { clip-path: inset(0 -38px 0 0); box-shadow: 6px 0 16px 4px rgba(0,0,0,0.12); }
|
|
65
|
+
/* mirror of the rule above, for stickyLast's trailing column (#327) */
|
|
66
|
+
& .crisp-table-scroll[data-scrolled-x-end] [data-sticky-edge-end] { clip-path: inset(0 0 0 -38px); box-shadow: -6px 0 16px 4px rgba(0,0,0,0.12); }
|
|
65
67
|
/* add-row + summary footer */
|
|
66
68
|
& .crisp-datatable-addcell { padding: 0; border-top: none; }
|
|
67
69
|
& .crisp-datatable-addrow { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; width: 100%; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["2"]}; border: none; border-top: 1px solid ${tokens_1.contract.borderSubtle}; background: ${tokens_1.contract.bgSurface}; color: ${tokens_1.contract.fgMuted}; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; cursor: pointer; transition: background-color .12s; }
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.FillDoc = void 0;
|
|
38
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
39
|
+
const React = __importStar(require("react"));
|
|
40
|
+
const core_1 = require("../../core");
|
|
41
|
+
const fillsheet_1 = require("../fillsheet");
|
|
42
|
+
const filldoc_recipe_1 = require("./filldoc.recipe");
|
|
43
|
+
/** Size a textarea to its content; see FillSheet's autoGrow for the reset. */
|
|
44
|
+
function autoGrow(el) {
|
|
45
|
+
if (!el)
|
|
46
|
+
return;
|
|
47
|
+
el.style.height = "auto";
|
|
48
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
49
|
+
}
|
|
50
|
+
let seq = 0;
|
|
51
|
+
const nextId = () => `b${++seq}`;
|
|
52
|
+
const emptyTable = () => ({
|
|
53
|
+
id: nextId(),
|
|
54
|
+
type: "table",
|
|
55
|
+
columns: [{ width: 160 }, { width: 320 }],
|
|
56
|
+
value: [
|
|
57
|
+
["", ""],
|
|
58
|
+
["", ""],
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* A page you can write on, of which a sheet is one block.
|
|
63
|
+
*
|
|
64
|
+
* FillSheet is the table; this is the document it sits in. The two share a
|
|
65
|
+
* look on purpose -- a real page, real margins, square corners, and nothing
|
|
66
|
+
* drawn that is not content -- and the same split between the person who
|
|
67
|
+
* MAKES a form and the person who fills it in: in `edit` you write the prose
|
|
68
|
+
* and add or remove blocks, in `fill` the prose is fixed and only the sheets
|
|
69
|
+
* take input.
|
|
70
|
+
*
|
|
71
|
+
* Every block's controls are hidden until the pointer is in that block. A
|
|
72
|
+
* page covered in handles and grips stops looking like a page, which is the
|
|
73
|
+
* whole thing this is for.
|
|
74
|
+
*/
|
|
75
|
+
exports.FillDoc = React.forwardRef(function FillDoc({ blocks, onBlocksChange, mode = "edit", page = "a4", label, addTextLabel = "Text", addHeadingLabel = "Heading", addTableLabel = "Table", removeLabel = "Remove", className, style, ...rest }, forwardedRef) {
|
|
76
|
+
const editable = mode === "edit";
|
|
77
|
+
const replace = (i, block) => {
|
|
78
|
+
const next = blocks.slice();
|
|
79
|
+
next[i] = block;
|
|
80
|
+
onBlocksChange?.(next);
|
|
81
|
+
};
|
|
82
|
+
const insertAfter = (i, block) => {
|
|
83
|
+
const next = blocks.slice();
|
|
84
|
+
next.splice(i + 1, 0, block);
|
|
85
|
+
onBlocksChange?.(next);
|
|
86
|
+
};
|
|
87
|
+
const removeAt = (i) => {
|
|
88
|
+
const next = blocks.slice();
|
|
89
|
+
next.splice(i, 1);
|
|
90
|
+
onBlocksChange?.(next);
|
|
91
|
+
};
|
|
92
|
+
return ((0, jsx_runtime_1.jsx)("div", { ...rest, ref: forwardedRef,
|
|
93
|
+
// The role rides WITH the name: `aria-label` on a role-less div is
|
|
94
|
+
// not exposed at all, and a bare `role="group"` on an unnamed page
|
|
95
|
+
// just adds an empty group to the tree. Either both or neither.
|
|
96
|
+
role: label ? "group" : undefined, "aria-label": label, className: (0, core_1.cx)((0, filldoc_recipe_1.filldocRecipe)({ page }), className), style: style, children: blocks.map((block, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-filldoc-block", children: [editable && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-filldoc-tools", children: (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": removeLabel, title: removeLabel, onClick: () => removeAt(i), children: "\u00D7" }) })), block.type === "table" ? ((0, jsx_runtime_1.jsx)(fillsheet_1.FillSheet, { label: block.label, mode: mode, surface: "panel", columns: block.columns, value: block.value, onValueChange: (value) => replace(i, { ...block, value }) })) : editable ? ((0, jsx_runtime_1.jsx)("textarea", { ref: autoGrow, className: (0, core_1.cx)("crisp-filldoc-writer", block.type === "heading"
|
|
97
|
+
? "crisp-filldoc-heading"
|
|
98
|
+
: "crisp-filldoc-text"), rows: 1, value: block.text, "aria-label": block.type === "heading" ? addHeadingLabel : addTextLabel, onChange: (e) => {
|
|
99
|
+
autoGrow(e.currentTarget);
|
|
100
|
+
replace(i, { ...block, text: e.target.value });
|
|
101
|
+
} })) : block.type === "heading" ? ((0, jsx_runtime_1.jsx)("h2", { className: "crisp-filldoc-heading", children: block.text })) : ((0, jsx_runtime_1.jsx)("p", { className: "crisp-filldoc-text", children: block.text })), editable && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-filldoc-insert", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-filldoc-insert-rule" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => insertAfter(i, { id: nextId(), type: "text", text: "" }), children: ["+ ", addTextLabel] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => insertAfter(i, { id: nextId(), type: "heading", text: "" }), children: ["+ ", addHeadingLabel] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => insertAfter(i, emptyTable()), children: ["+ ", addTableLabel] }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-filldoc-insert-rule" })] }))] }, block.id))) }));
|
|
102
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spec = exports.filldocRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
/**
|
|
7
|
+
* FillDoc — a page of blocks, of which a sheet is one.
|
|
8
|
+
*
|
|
9
|
+
* The measurement rule is waived here for the same reason it is waived for
|
|
10
|
+
* FillSheet, and by the same decision: Attio has no document editor. It spends
|
|
11
|
+
* the measured scale everywhere a value exists on it.
|
|
12
|
+
*
|
|
13
|
+
* The look is FillSheet's, continued: a real page with real margins, square
|
|
14
|
+
* corners, and nothing on it that is not content. A block shows no chrome at
|
|
15
|
+
* rest — no handle, no outline, no drag grip — because a page covered in
|
|
16
|
+
* controls stops looking like a page. The controls appear where the pointer
|
|
17
|
+
* is, and only in `edit`.
|
|
18
|
+
*/
|
|
19
|
+
const line = tokens_1.contract.borderDefault;
|
|
20
|
+
// A4 at 96dpi, in the unit that actually means A4. `mm` is a real CSS unit,
|
|
21
|
+
// so this prints at the size it says.
|
|
22
|
+
const PAGE_WIDTH = "210mm";
|
|
23
|
+
const PAGE_MIN_HEIGHT = "297mm";
|
|
24
|
+
const PAGE_MARGIN = "20mm";
|
|
25
|
+
const spec = (0, core_1.defineSpec)({
|
|
26
|
+
name: "filldoc",
|
|
27
|
+
base: {
|
|
28
|
+
display: "flex",
|
|
29
|
+
flexDirection: "column",
|
|
30
|
+
gap: tokens_1.contract.space["4"],
|
|
31
|
+
boxSizing: "border-box",
|
|
32
|
+
width: PAGE_WIDTH,
|
|
33
|
+
minHeight: PAGE_MIN_HEIGHT,
|
|
34
|
+
padding: PAGE_MARGIN,
|
|
35
|
+
margin: "0 auto",
|
|
36
|
+
background: tokens_1.contract.bgSurface,
|
|
37
|
+
fontFamily: tokens_1.contract.font.sans,
|
|
38
|
+
fontSize: tokens_1.contract.text.lg,
|
|
39
|
+
lineHeight: "22px",
|
|
40
|
+
color: tokens_1.contract.fgPrimary,
|
|
41
|
+
// Paper is not rounded, and the lift is what says it is a sheet rather
|
|
42
|
+
// than a panel in a layout.
|
|
43
|
+
borderRadius: "0",
|
|
44
|
+
boxShadow: tokens_1.elevation.floating,
|
|
45
|
+
},
|
|
46
|
+
parts: [
|
|
47
|
+
{ selector: "&:focus, &:focus-visible", decls: { outline: "none" } },
|
|
48
|
+
{
|
|
49
|
+
slot: "block",
|
|
50
|
+
selector: "& .crisp-filldoc-block",
|
|
51
|
+
decls: { position: "relative" },
|
|
52
|
+
},
|
|
53
|
+
// --- text ------------------------------------------------------------
|
|
54
|
+
{
|
|
55
|
+
slot: "heading",
|
|
56
|
+
selector: "& .crisp-filldoc-heading",
|
|
57
|
+
decls: {
|
|
58
|
+
margin: "0",
|
|
59
|
+
fontSize: tokens_1.contract.text.xl,
|
|
60
|
+
fontWeight: tokens_1.contract.weight.semibold,
|
|
61
|
+
lineHeight: "26px",
|
|
62
|
+
color: tokens_1.contract.fgPrimary,
|
|
63
|
+
whiteSpace: "pre-wrap",
|
|
64
|
+
overflowWrap: "break-word",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
slot: "paragraph",
|
|
69
|
+
selector: "& .crisp-filldoc-text",
|
|
70
|
+
decls: {
|
|
71
|
+
margin: "0",
|
|
72
|
+
fontSize: tokens_1.contract.text.lg,
|
|
73
|
+
lineHeight: "22px",
|
|
74
|
+
color: tokens_1.contract.fgPrimary,
|
|
75
|
+
whiteSpace: "pre-wrap",
|
|
76
|
+
overflowWrap: "break-word",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
// The editor for a text block is the text: same font, same line box, no
|
|
80
|
+
// box of its own. Anything else and the words move when you click them.
|
|
81
|
+
{
|
|
82
|
+
slot: "writer",
|
|
83
|
+
selector: "& .crisp-filldoc-writer",
|
|
84
|
+
decls: {
|
|
85
|
+
display: "block",
|
|
86
|
+
width: "100%",
|
|
87
|
+
margin: "0",
|
|
88
|
+
padding: "0",
|
|
89
|
+
border: "none",
|
|
90
|
+
outline: "none",
|
|
91
|
+
background: "transparent",
|
|
92
|
+
fontFamily: "inherit",
|
|
93
|
+
fontSize: "inherit",
|
|
94
|
+
fontWeight: "inherit",
|
|
95
|
+
lineHeight: "inherit",
|
|
96
|
+
color: "inherit",
|
|
97
|
+
resize: "none",
|
|
98
|
+
overflow: "hidden",
|
|
99
|
+
// the page turns text selection off so a block drag does not smear
|
|
100
|
+
// one across it; the writer has to opt back in
|
|
101
|
+
userSelect: "text",
|
|
102
|
+
WebkitUserSelect: "text",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
// --- the between-blocks control --------------------------------------
|
|
106
|
+
// A page covered in buttons stops reading as a page, so the only thing
|
|
107
|
+
// that ever appears is a hairline with a + on it, in the gap the pointer
|
|
108
|
+
// is actually in.
|
|
109
|
+
{
|
|
110
|
+
slot: "inserter",
|
|
111
|
+
selector: "& .crisp-filldoc-insert",
|
|
112
|
+
decls: {
|
|
113
|
+
position: "absolute",
|
|
114
|
+
insetInlineStart: "0",
|
|
115
|
+
insetInlineEnd: "0",
|
|
116
|
+
insetBlockEnd: `calc(${tokens_1.contract.space["4"]} / -2)`,
|
|
117
|
+
height: tokens_1.contract.space["4"],
|
|
118
|
+
display: "flex",
|
|
119
|
+
alignItems: "center",
|
|
120
|
+
gap: tokens_1.contract.space["2"],
|
|
121
|
+
opacity: "0",
|
|
122
|
+
transition: "opacity .1s",
|
|
123
|
+
cursor: "pointer",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
selector: "& .crisp-filldoc-block:hover .crisp-filldoc-insert, & .crisp-filldoc-insert:focus-within",
|
|
128
|
+
decls: { opacity: "1" },
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
selector: "& .crisp-filldoc-insert-rule",
|
|
132
|
+
decls: { flex: "1", height: "1px", background: line },
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
slot: "insertButton",
|
|
136
|
+
selector: "& .crisp-filldoc-insert button",
|
|
137
|
+
decls: {
|
|
138
|
+
display: "inline-flex",
|
|
139
|
+
alignItems: "center",
|
|
140
|
+
height: "20px",
|
|
141
|
+
padding: `0 ${tokens_1.contract.space["1.5"]}`,
|
|
142
|
+
border: "none",
|
|
143
|
+
borderRadius: tokens_1.contract.radius.sm,
|
|
144
|
+
background: tokens_1.contract.bgRaised,
|
|
145
|
+
fontFamily: "inherit",
|
|
146
|
+
fontSize: tokens_1.contract.text.sm,
|
|
147
|
+
color: tokens_1.contract.fgMuted,
|
|
148
|
+
cursor: "pointer",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
selector: "& .crisp-filldoc-insert button:hover",
|
|
153
|
+
decls: { background: tokens_1.contract.bgNeutralWeak, color: tokens_1.contract.fgPrimary },
|
|
154
|
+
},
|
|
155
|
+
// --- the block's own controls ----------------------------------------
|
|
156
|
+
{
|
|
157
|
+
slot: "blockTools",
|
|
158
|
+
selector: "& .crisp-filldoc-tools",
|
|
159
|
+
decls: {
|
|
160
|
+
position: "absolute",
|
|
161
|
+
insetInlineEnd: "100%",
|
|
162
|
+
insetBlockStart: "0",
|
|
163
|
+
paddingInlineEnd: tokens_1.contract.space["2"],
|
|
164
|
+
display: "flex",
|
|
165
|
+
gap: tokens_1.contract.space["1"],
|
|
166
|
+
opacity: "0",
|
|
167
|
+
transition: "opacity .1s",
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
selector: "& .crisp-filldoc-block:hover .crisp-filldoc-tools, & .crisp-filldoc-tools:focus-within",
|
|
172
|
+
decls: { opacity: "1" },
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
selector: "& .crisp-filldoc-tools button",
|
|
176
|
+
decls: {
|
|
177
|
+
width: "20px",
|
|
178
|
+
height: "20px",
|
|
179
|
+
display: "inline-flex",
|
|
180
|
+
alignItems: "center",
|
|
181
|
+
justifyContent: "center",
|
|
182
|
+
padding: "0",
|
|
183
|
+
border: "none",
|
|
184
|
+
borderRadius: tokens_1.contract.radius.sm,
|
|
185
|
+
background: "transparent",
|
|
186
|
+
fontFamily: "inherit",
|
|
187
|
+
fontSize: tokens_1.contract.text.sm,
|
|
188
|
+
color: tokens_1.contract.fgMuted,
|
|
189
|
+
cursor: "pointer",
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
selector: "& .crisp-filldoc-tools button:hover",
|
|
194
|
+
decls: { background: tokens_1.contract.bgRaised, color: tokens_1.contract.fgPrimary },
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
variants: {
|
|
198
|
+
page: {
|
|
199
|
+
// A real sheet of A4. The default, because the point is a document.
|
|
200
|
+
a4: {},
|
|
201
|
+
// Grows to its content and fills its container instead, for a document
|
|
202
|
+
// embedded in a screen rather than printed.
|
|
203
|
+
auto: { width: "100%", minHeight: "0", maxWidth: PAGE_WIDTH },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
defaultVariants: { page: "a4" },
|
|
207
|
+
});
|
|
208
|
+
exports.spec = spec;
|
|
209
|
+
const config = (0, core_1.specToRecipe)(spec);
|
|
210
|
+
exports.filldocRecipe = Object.assign((0, core_1.resolveRecipe)(config), { config });
|
|
211
|
+
exports.default = config;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.docToMarkdown = exports.docToHTML = exports.filldocRecipe = exports.FillDoc = void 0;
|
|
4
|
+
var filldoc_1 = require("./filldoc");
|
|
5
|
+
Object.defineProperty(exports, "FillDoc", { enumerable: true, get: function () { return filldoc_1.FillDoc; } });
|
|
6
|
+
var filldoc_recipe_1 = require("./filldoc.recipe");
|
|
7
|
+
Object.defineProperty(exports, "filldocRecipe", { enumerable: true, get: function () { return filldoc_recipe_1.filldocRecipe; } });
|
|
8
|
+
var serialize_1 = require("./serialize");
|
|
9
|
+
Object.defineProperty(exports, "docToHTML", { enumerable: true, get: function () { return serialize_1.docToHTML; } });
|
|
10
|
+
Object.defineProperty(exports, "docToMarkdown", { enumerable: true, get: function () { return serialize_1.docToMarkdown; } });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.docToHTML = docToHTML;
|
|
4
|
+
exports.docToMarkdown = docToMarkdown;
|
|
5
|
+
const serialize_1 = require("../fillsheet/serialize");
|
|
6
|
+
/**
|
|
7
|
+
* The whole document, in the formats a document reads.
|
|
8
|
+
*
|
|
9
|
+
* The same trick as the sheet's serialisers: plain functions over plain data,
|
|
10
|
+
* no React and no DOM, so a server route can render an export. A table block
|
|
11
|
+
* hands straight off to the sheet's own writer, so there is one implementation
|
|
12
|
+
* of table escaping rather than two that drift.
|
|
13
|
+
*/
|
|
14
|
+
const escapeHtml = (s) => s
|
|
15
|
+
.replace(/&/g, "&")
|
|
16
|
+
.replace(/</g, "<")
|
|
17
|
+
.replace(/>/g, ">")
|
|
18
|
+
.replace(/"/g, """);
|
|
19
|
+
/** An HTML document body: what Word, Google Docs and Pages paste as a document. */
|
|
20
|
+
function docToHTML(blocks) {
|
|
21
|
+
return blocks
|
|
22
|
+
.map((b) => {
|
|
23
|
+
if (b.type === "table")
|
|
24
|
+
return (0, serialize_1.toHTML)(b.value, { columns: b.columns });
|
|
25
|
+
const text = escapeHtml(b.text).replace(/\r?\n/g, "<br>");
|
|
26
|
+
return b.type === "heading" ? `<h2>${text}</h2>` : `<p>${text}</p>`;
|
|
27
|
+
})
|
|
28
|
+
.join("\n");
|
|
29
|
+
}
|
|
30
|
+
/** The same document as Markdown. */
|
|
31
|
+
function docToMarkdown(blocks) {
|
|
32
|
+
return blocks
|
|
33
|
+
.map((b) => {
|
|
34
|
+
if (b.type === "table")
|
|
35
|
+
return (0, serialize_1.toMarkdown)(b.value, { columns: b.columns });
|
|
36
|
+
return b.type === "heading" ? `## ${b.text}` : b.text;
|
|
37
|
+
})
|
|
38
|
+
.join("\n\n");
|
|
39
|
+
}
|