@sanity/table
Version:
Table schema type and input component for Sanity Studio
433 lines (432 loc) • 16.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: !0 });
var sanity = require("sanity"), jsxRuntime = require("react/jsx-runtime"), icons = require("@sanity/icons"), ui = require("@sanity/ui"), react = require("react"), getRandomValues, rnds8 = new Uint8Array(16);
function rng() {
if (!getRandomValues && (getRandomValues = typeof crypto < "u" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto) || typeof msCrypto < "u" && typeof msCrypto.getRandomValues == "function" && msCrypto.getRandomValues.bind(msCrypto), !getRandomValues))
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
return getRandomValues(rnds8);
}
var REGEX = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
function validate(uuid) {
return typeof uuid == "string" && REGEX.test(uuid);
}
var byteToHex = [];
for (var i = 0; i < 256; ++i)
byteToHex.push((i + 256).toString(16).substr(1));
function stringify(arr) {
var offset = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0, uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
if (!validate(uuid))
throw TypeError("Stringified UUID is invalid");
return uuid;
}
function v4(options, buf, offset) {
options = options || {};
var rnds = options.random || (options.rng || rng)();
return rnds[6] = rnds[6] & 15 | 64, rnds[8] = rnds[8] & 63 | 128, stringify(rnds);
}
const TableInput = (props) => {
var _a2;
const updateCell = props.updateCell, renderRowCell = (rowIndex) => function(cell, cellIndex) {
return /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx(
ui.TextInput,
{
fontSize: 1,
padding: 3,
value: cell,
onChange: (e) => updateCell(e, rowIndex, cellIndex)
}
) }, `cell-${rowIndex}-${cellIndex}`);
}, renderRow = (row, rowIndex) => {
const renderCell = renderRowCell(rowIndex);
return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
row.cells.map(renderCell),
/* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginLeft: 1, style: { textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
ui.Button,
{
icon: icons.RemoveIcon,
padding: 2,
onClick: () => props.removeRow(rowIndex),
mode: "bleed"
}
) }) }, rowIndex)
] }, `row-${rowIndex}`);
};
return /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsxs("tbody", { children: [
props.rows.map(renderRow),
/* @__PURE__ */ jsxRuntime.jsx("tr", { children: (((_a2 = props.rows[0]) == null ? void 0 : _a2.cells) || []).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginTop: 1, style: { textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
ui.Button,
{
icon: icons.RemoveIcon,
padding: 2,
onClick: () => props.removeColumn(i),
mode: "bleed"
}
) }) }, i)) })
] }) });
}, TableMenu = (props) => {
const { remove: handleRemove } = props, [dialog, setDialog] = react.useState(null), [count, setCount] = react.useState(""), updateCount = (e) => {
setCount(e.currentTarget.value);
}, addRows = () => {
setDialog({ type: "rows", callback: (c) => props.addRows(c) });
}, addRowAt = () => {
setDialog({ type: "rows", callback: (index) => props.addRowAt(index) });
}, addColumns = () => {
setDialog({
type: "columns",
callback: (c) => props.addColumns(c)
});
}, addColumnsAt = () => {
setDialog({ type: "columns", callback: (index) => props.addColumnAt(index) });
}, onConfirm = () => {
const parsedCount = parseInt(count != null ? count : "0", 10);
parsedCount < 100 && (setDialog(null), dialog == null || dialog.callback(parsedCount), setCount(void 0));
};
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
dialog && /* @__PURE__ */ jsxRuntime.jsx(
ui.Dialog,
{
header: `Add ${dialog.type}`,
id: "dialog-add",
onClose: () => setDialog(null),
zOffset: 1e3,
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { padding: 4, children: [
/* @__PURE__ */ jsxRuntime.jsx(
ui.TextInput,
{
style: { textAlign: "left" },
fontSize: 2,
padding: 3,
type: "number",
value: count,
onChange: updateCount
}
),
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Inline, { space: 1, style: { textAlign: "right" }, children: [
/* @__PURE__ */ jsxRuntime.jsx(
ui.Button,
{
text: "Cancel",
mode: "ghost",
onClick: () => setDialog(null)
}
),
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { text: "Confirm", tone: "critical", onClick: onConfirm })
] }) })
] })
}
),
/* @__PURE__ */ jsxRuntime.jsx(
ui.MenuButton,
{
button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { icon: icons.ControlsIcon, fontSize: 1, padding: 2, mode: "ghost" }),
id: "menu-button-example",
menu: /* @__PURE__ */ jsxRuntime.jsxs(ui.Menu, { children: [
/* @__PURE__ */ jsxRuntime.jsx(
ui.MenuItem,
{
icon: icons.AddIcon,
fontSize: 1,
text: "Add Row(s)",
onClick: addRows
}
),
/* @__PURE__ */ jsxRuntime.jsx(
ui.MenuItem,
{
icon: icons.AddIcon,
fontSize: 1,
text: "Add Row At Index",
onClick: addRowAt
}
),
/* @__PURE__ */ jsxRuntime.jsx(
ui.MenuItem,
{
icon: icons.AddIcon,
fontSize: 1,
text: "Add Column(s)",
onClick: addColumns
}
),
/* @__PURE__ */ jsxRuntime.jsx(
ui.MenuItem,
{
icon: icons.AddIcon,
fontSize: 1,
text: "Add Column At Index",
onClick: addColumnsAt
}
),
/* @__PURE__ */ jsxRuntime.jsx(ui.MenuDivider, {}),
/* @__PURE__ */ jsxRuntime.jsx(
ui.MenuItem,
{
icon: icons.WarningOutlineIcon,
fontSize: 1,
text: "Remove",
tone: "critical",
onClick: handleRemove
}
)
] }),
popover: { placement: props.placement }
}
)
] });
};
var __defProp = Object.defineProperty, __defProps = Object.defineProperties, __getOwnPropDescs = Object.getOwnPropertyDescriptors, __getOwnPropSymbols = Object.getOwnPropertySymbols, __hasOwnProp = Object.prototype.hasOwnProperty, __propIsEnum = Object.prototype.propertyIsEnumerable, __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value, __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
__hasOwnProp.call(b, prop) && __defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b))
__propIsEnum.call(b, prop) && __defNormalProp(a, prop, b[prop]);
return a;
}, __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)), _a;
const deepClone = (_a = globalThis.structuredClone) != null ? _a : (data) => JSON.parse(JSON.stringify(data)), TableComponent = (props) => {
var _a2, _b, _c;
const { rowType = "tableRow", value, onChange } = props, [dialog, setDialog] = react.useState(null), updateValue = (v) => onChange(sanity.set(v)), resetValue = () => onChange(sanity.unset()), createTable = () => {
const newValue = {
rows: [
{
_type: rowType,
_key: v4(),
cells: ["", ""]
},
{
_type: rowType,
_key: v4(),
cells: ["", ""]
}
]
};
return updateValue(__spreadValues(__spreadValues({}, value), newValue));
}, confirmRemoveTable = () => {
setDialog({ type: "table", callback: removeTable });
}, removeTable = () => {
resetValue(), setDialog(null);
}, addRows = (count = 1) => {
var _a3;
if (!value)
return;
const newValue = deepClone(value), columnCount = (_a3 = value == null ? void 0 : value.rows[0].cells.length) != null ? _a3 : 0;
for (let i = 0; i < count; i++)
newValue.rows.push({
_type: rowType,
_key: v4(),
cells: Array(columnCount).fill("")
});
return updateValue(newValue);
}, addRowAt = (index = 0) => {
if (!value)
return;
const newValue = deepClone(value), columnCount = value.rows[0].cells.length;
return newValue.rows.splice(index, 0, {
_type: rowType,
_key: v4(),
cells: Array(columnCount).fill("")
}), updateValue(newValue);
}, removeRow = (index) => {
if (!value)
return;
const newValue = deepClone(value);
newValue.rows.splice(index, 1), updateValue(newValue), setDialog(null);
}, confirmRemoveRow = (index) => {
if (value)
return value.rows.length <= 1 ? confirmRemoveTable() : setDialog({ type: "row", callback: () => removeRow(index) });
}, confirmRemoveColumn = (index) => {
if (value)
return value.rows[0].cells.length <= 1 ? confirmRemoveTable() : setDialog({ type: "column", callback: () => removeColumn(index) });
}, addColumns = (count) => {
if (!value)
return;
const newValue = deepClone(value);
return newValue.rows.forEach((_, i) => {
for (let j = 0; j < count; j++)
newValue.rows[i].cells.push("");
}), updateValue(newValue);
}, addColumnAt = (index) => {
if (!value)
return;
const newValue = deepClone(value);
return newValue.rows.forEach((_, i) => {
newValue.rows[i].cells.splice(index, 0, "");
}), updateValue(newValue);
}, removeColumn = (index) => {
if (!value)
return;
const newValue = deepClone(value);
newValue.rows.forEach((row) => {
row.cells.splice(index, 1);
}), updateValue(newValue), setDialog(null);
}, updateCell = (e, rowIndex, cellIndex) => {
if (!value)
return;
const newValue = deepClone(value);
return newValue.rows[rowIndex].cells[cellIndex] = e.target.value, updateValue(newValue);
};
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
dialog && /* @__PURE__ */ jsxRuntime.jsx(
ui.Dialog,
{
header: `Remove ${dialog.type}`,
id: "dialog-remove",
onClose: () => setDialog(null),
zOffset: 1e3,
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { padding: 4, children: [
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { children: [
"Are you sure you want to remove this ",
dialog.type,
"?"
] }),
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Inline, { space: 1, style: { textAlign: "right" }, children: [
/* @__PURE__ */ jsxRuntime.jsx(
ui.Button,
{
text: "Cancel",
mode: "ghost",
onClick: () => setDialog(null)
}
),
/* @__PURE__ */ jsxRuntime.jsx(
ui.Button,
{
text: "Confirm",
tone: "critical",
onClick: () => dialog.callback()
}
)
] }) })
] })
}
),
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { justify: "flex-end", children: ((_a2 = value == null ? void 0 : value.rows) == null ? void 0 : _a2.length) && /* @__PURE__ */ jsxRuntime.jsx(
TableMenu,
{
addColumns,
addColumnAt,
addRows,
addRowAt,
remove: confirmRemoveTable,
placement: "left"
}
) }) }),
((_b = value == null ? void 0 : value.rows) == null ? void 0 : _b.length) && /* @__PURE__ */ jsxRuntime.jsx(
TableInput,
{
rows: value.rows,
removeRow: confirmRemoveRow,
removeColumn: confirmRemoveColumn,
updateCell
}
),
(!value || !((_c = value == null ? void 0 : value.rows) != null && _c.length)) && /* @__PURE__ */ jsxRuntime.jsx(ui.Inline, { space: 1, children: /* @__PURE__ */ jsxRuntime.jsx(
ui.Button,
{
fontSize: 1,
padding: 3,
icon: icons.AddIcon,
text: "Create Table",
tone: "primary",
mode: "ghost",
onClick: createTable
}
) })
] });
};
function createTableComponent(rowType) {
return function(props) {
return /* @__PURE__ */ jsxRuntime.jsx(TableComponent, __spreadProps(__spreadValues({}, props), { rowType }));
};
}
function TableIcon() {
return /* @__PURE__ */ jsxRuntime.jsx(
"svg",
{
width: "1em",
height: "1em",
viewBox: "0 0 25 25",
fill: "none",
stroke: "currentColor",
strokeWidth: "1.2",
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3h18v18H3zM21 9H3M21 15H3M12 3v18" })
}
);
}
const Table = ({ rows }) => {
const numCols = rows.length === 0 ? 0 : rows[0].cells.length;
return /* @__PURE__ */ jsxRuntime.jsx(ui.Grid, { columns: numCols, padding: 2, children: rows.map(
(row) => row.cells.map((cell, i) => /* @__PURE__ */ jsxRuntime.jsx(
ui.Card,
{
padding: 2,
style: { outline: "1px solid #DFE2E9" },
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { style: { textOverflow: "elipsis" }, children: cell })
},
row._key + i
))
) });
}, TablePreview = (props) => {
var _a2;
const { schemaType, rows = [], title = "Title missing" } = props;
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Inline, { space: 3, children: [
/* @__PURE__ */ jsxRuntime.jsx(ui.Card, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: 4, children: /* @__PURE__ */ jsxRuntime.jsx(TableIcon, {}) }) }),
/* @__PURE__ */ jsxRuntime.jsx(ui.Card, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: (_a2 = schemaType == null ? void 0 : schemaType.title) != null ? _a2 : title }) })
] }) }),
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 2, children: rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { muted: !0, children: "Empty Table" }) : /* @__PURE__ */ jsxRuntime.jsx(Table, { rows }) })
] });
}, table = sanity.definePlugin((config) => {
const tableRowSchema = sanity.defineType({
title: "Table Row",
name: (config == null ? void 0 : config.rowType) || "tableRow",
type: "object",
fields: [
{
name: "cells",
type: "array",
of: [{ type: "string" }]
}
]
}), tableSchema = sanity.defineType({
title: "Table",
name: "table",
type: "object",
fields: [
{
name: "rows",
type: "array",
of: [
{
type: tableRowSchema.name
}
]
}
],
components: {
/* eslint-disable @typescript-eslint/no-explicit-any */
input: createTableComponent(tableRowSchema.name),
preview: TablePreview
/* eslint-enable @typescript-eslint/no-explicit-any */
},
preview: {
select: {
rows: "rows",
title: "title"
},
prepare: ({ title, rows = [] }) => ({
title,
rows
})
}
});
return {
name: "table",
schema: {
types: [tableRowSchema, tableSchema]
}
};
});
exports.TableComponent = TableComponent;
exports.TablePreview = TablePreview;
exports.table = table;
//# sourceMappingURL=index.cjs.map