@zuilib/text-editor 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## 0.4.0
4
4
 
5
+ - **Table density**, Slab-style: the floating table toolbar (caret in a
6
+ table) gains *compact* / *comfortable* (default) / *spacious* row padding
7
+ and font size, next to the width toggle
8
+ - The settings comment above a GFM table now lists every non-default
9
+ setting, e.g. `<!-- width: content; density: compact -->`; the 0.3.1
10
+ `<!-- width: content -->` form is unchanged for width-only tables
11
+ - `useMarkdownEditor()` gains `tableDensity` / `setTableDensity`
12
+ - New exports: `TableDensity`, `TableSettings`, `DEFAULT_TABLE_SETTINGS`,
13
+ `$getTableSettings` / `$setTableSettings`, `$getTableDensity` /
14
+ `$setTableDensity`, `parseTableSettingsMarker` /
15
+ `formatTableSettingsMarker`. `TABLE_WIDTH_MARKER` is deprecated in favour
16
+ of `formatTableSettingsMarker`
17
+
18
+ ## 0.3.1
19
+
5
20
  - **Block width** for tables and drawings: switch any table or drawing
6
21
  between *full width* (spans the editor, the previous behaviour) and
7
22
  *content width* (shrinks to fit its columns / shapes, left-aligned with
package/README.md CHANGED
@@ -13,8 +13,8 @@ document outline, and collapsible sections.
13
13
  blockquotes, links, fenced code (with syntax highlighting), tables
14
14
  - **Tables**: GFM pipe tables, edited in place (tab between cells, ranges,
15
15
  inline formatting), styled like Claude artifacts
16
- - **Block width**: tables and diagrams switch between full width and
17
- content width, Slab-style
16
+ - **Table settings**: full/content width and row density per table;
17
+ diagrams switch between full and content width too — Slab-style
18
18
  - **Diagrams**: a drawing canvas embedded in the document — cards with
19
19
  attached text slots, bound arrows that follow their cards, elbow and
20
20
  multi-waypoint routing, labels, color palettes, dark mode
@@ -98,26 +98,33 @@ Editing is in place: Tab/arrows between cells, cell range selection, inline
98
98
  formatting inside cells. Cell content is single-line in markdown; newlines
99
99
  are escaped as `\n`.
100
100
 
101
- ### Full width vs content width
101
+ ### Table settings: width and density
102
102
 
103
- Tables span the editor by default. Place the caret in a table and a small
104
- toggle floats at its top-right corner: **full width** (columns share the
105
- editor width) or **content width** (the table shrinks to fit its columns,
106
- left-aligned with the text, never wider than the editor). The setting is
107
- persisted as an HTML comment on the line directly above the table — other
108
- markdown renderers hide it:
103
+ Place the caret in a table and a small toolbar floats at its top-right
104
+ corner:
105
+
106
+ - **Width** *full* (default: columns share the editor width) or
107
+ *content* (the table shrinks to fit its columns, left-aligned with the
108
+ text, never wider than the editor).
109
+ - **Density** — *compact*, *comfortable* (default), or *spacious* cell
110
+ padding and font size.
111
+
112
+ Non-default settings are persisted as one HTML comment on the line directly
113
+ above the table — other markdown renderers hide it:
109
114
 
110
115
  ```md
111
- <!-- width: content -->
116
+ <!-- width: content; density: compact -->
112
117
  | Key | Value |
113
118
  | --- | --- |
114
119
  | Region | eu-west-1 |
115
120
  ```
116
121
 
117
- Programmatic access: `useMarkdownEditor().tableWidth` / `setTableWidth`
118
- (for custom toolbars), or `$getTableWidth(node)` / `$setTableWidth(node,
119
- width)` inside `editor.update`. The marker string is exported as
120
- `TABLE_WIDTH_MARKER`.
122
+ Programmatic access: `useMarkdownEditor().tableWidth` / `setTableWidth` and
123
+ `tableDensity` / `setTableDensity` (for custom toolbars), or
124
+ `$getTableSettings(node)` / `$setTableSettings(node, { width, density })`
125
+ inside `editor.update`. `parseTableSettingsMarker` /
126
+ `formatTableSettingsMarker` convert between the comment line and a
127
+ `TableSettings` object.
121
128
 
122
129
  ## Toolbar & custom toolbars
123
130
 
@@ -203,8 +210,9 @@ function BoldButton() {
203
210
 
204
211
  It returns `editor` (the Lexical instance), `activeFormats`,
205
212
  `toggleFormat`, `insertTable`, `insertDrawing`, `tableWidth` /
206
- `setTableWidth` (width of the table containing the selection, `null`
207
- outside tables), `canUndo`, `canRedo`, `undo`, `redo`.
213
+ `setTableWidth` and `tableDensity` / `setTableDensity` (settings of the
214
+ table containing the selection, `null` outside tables), `canUndo`,
215
+ `canRedo`, `undo`, `redo`.
208
216
 
209
217
  ## Diagrams (drawing canvas)
210
218
 
@@ -269,7 +277,9 @@ diagrams:
269
277
  | `Toolbar`, `ToolbarButton`, `ToolbarDivider`, `FormatButtons`, `InsertButtons`, `HistoryButtons` | Toolbar primitives |
270
278
  | `@zuilib/text-editor/styles.css` | Editor chrome + theme styles (required) |
271
279
  | `TABLE` | GFM table markdown transformer |
272
- | `BlockWidth`, `$getTableWidth`, `$setTableWidth`, `$getSelectedTable`, `TABLE_WIDTH_MARKER` | Table full/content width helpers |
280
+ | `BlockWidth`, `TableDensity`, `TableSettings`, `DEFAULT_TABLE_SETTINGS` | Table setting types |
281
+ | `$getTableSettings`, `$setTableSettings`, `$getTableWidth`, `$setTableWidth`, `$getTableDensity`, `$setTableDensity`, `$getSelectedTable` | Table setting helpers (inside `editor.update`/`read`) |
282
+ | `parseTableSettingsMarker`, `formatTableSettingsMarker` | Marker comment ⇄ `TableSettings` |
273
283
  | `DRAWING`, `DrawingNode`, `$createDrawingNode`, `$isDrawingNode` | Drawing node + markdown transformer |
274
284
  | `FRONTMATTER`, `FrontmatterNode`, `$createFrontmatterNode`, `$isFrontmatterNode` | Frontmatter node + transformer |
275
285
  | `DrawingData`, `DrawingShape`, `DrawingShapeType` | Drawing payload types |
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, ReactElement } from 'react';
3
3
  import { LexicalEditor, TextFormatType, ElementNode, NodeKey, EditorConfig, SerializedElementNode, LexicalNode, DecoratorNode, Spread, SerializedLexicalNode, DOMExportOutput, DOMConversionMap } from 'lexical';
4
- import { MultilineElementTransformer, ElementTransformer } from '@lexical/markdown';
5
4
  import { TableNode } from '@lexical/table';
5
+ import { MultilineElementTransformer, ElementTransformer } from '@lexical/markdown';
6
6
 
7
7
  type EditorMode = 'edit-raw' | 'edit-md' | 'view';
8
8
  type EditorRootProps = Readonly<{
@@ -48,6 +48,28 @@ declare function OutlinePlugin(): ReactElement;
48
48
  */
49
49
  type BlockWidth = 'full' | 'content';
50
50
 
51
+ /** Cell padding / font size preset */
52
+ type TableDensity = 'compact' | 'comfortable' | 'spacious';
53
+ type TableSettings = Readonly<{
54
+ width: BlockWidth;
55
+ density: TableDensity;
56
+ }>;
57
+ declare const DEFAULT_TABLE_SETTINGS: TableSettings;
58
+ declare function $getTableSettings(table: TableNode): TableSettings;
59
+ declare function $setTableSettings(table: TableNode, settings: Partial<TableSettings>): void;
60
+ declare function $getTableWidth(table: TableNode): BlockWidth;
61
+ declare function $setTableWidth(table: TableNode, width: BlockWidth): void;
62
+ declare function $getTableDensity(table: TableNode): TableDensity;
63
+ declare function $setTableDensity(table: TableNode, density: TableDensity): void;
64
+ /** The table containing the current selection (caret or cell range), if any */
65
+ declare function $getSelectedTable(): TableNode | null;
66
+ /** @deprecated Use `formatTableSettingsMarker({ ...DEFAULT_TABLE_SETTINGS, width: 'content' })` */
67
+ declare const TABLE_WIDTH_MARKER = "<!-- width: content -->";
68
+ /** Settings encoded in a marker line, or null if the text is not a marker */
69
+ declare function parseTableSettingsMarker(text: string): Partial<TableSettings> | null;
70
+ /** Marker line for the given settings, or null when everything is default */
71
+ declare function formatTableSettingsMarker(settings: TableSettings): string | null;
72
+
51
73
  type MarkdownEditorApi = Readonly<{
52
74
  /** The underlying Lexical editor, for dispatching your own commands */
53
75
  editor: LexicalEditor;
@@ -63,6 +85,10 @@ type MarkdownEditorApi = Readonly<{
63
85
  tableWidth: BlockWidth | null;
64
86
  /** Resize the table containing the selection; no-op outside tables */
65
87
  setTableWidth: (width: BlockWidth) => void;
88
+ /** Density of the table containing the selection; `null` outside tables */
89
+ tableDensity: TableDensity | null;
90
+ /** Change the density of the table containing the selection; no-op outside tables */
91
+ setTableDensity: (density: TableDensity) => void;
66
92
  canUndo: boolean;
67
93
  canRedo: boolean;
68
94
  undo: () => void;
@@ -274,23 +300,12 @@ declare const DRAWING: MultilineElementTransformer;
274
300
  * Markdown transformer for GFM tables. Adapted from the Lexical playground.
275
301
  * Rows are matched line-by-line on import and stitched into a single
276
302
  * TableNode; the divider row promotes the row above it to a header row.
277
- * A `<!-- width: content -->` line directly above the table marks it as
278
- * content-width (see tableWidth.ts); it is consumed on import and re-emitted
279
- * on export.
303
+ * A `<!-- width: content; density: compact -->` settings line directly
304
+ * above the table (see tableSettings.ts) is consumed on import and
305
+ * re-emitted on export.
280
306
  */
281
307
  declare const TABLE: ElementTransformer;
282
308
 
283
- /**
284
- * A content-width table is persisted in markdown as a GFM table preceded by
285
- * this HTML comment on its own line. Renderers that don't know the marker
286
- * simply hide the comment.
287
- */
288
- declare const TABLE_WIDTH_MARKER = "<!-- width: content -->";
289
- declare function $getTableWidth(table: TableNode): BlockWidth;
290
- declare function $setTableWidth(table: TableNode, width: BlockWidth): void;
291
- /** The table containing the current selection (caret or cell range), if any */
292
- declare function $getSelectedTable(): TableNode | null;
293
-
294
309
  /**
295
310
  * JSON Schema (draft-07) for the payload of a ```drawing fenced block.
296
311
  *
@@ -427,4 +442,4 @@ declare const DRAWING_DATA_JSON_SCHEMA: {
427
442
  };
428
443
  };
429
444
 
430
- export { $createDrawingNode, $createFrontmatterNode, $getSelectedTable, $getTableWidth, $isDrawingNode, $isFrontmatterNode, $setTableWidth, type BlockWidth, DRAWING, DRAWING_DATA_JSON_SCHEMA, type DrawingData, DrawingNode, type DrawingShape, type DrawingShapeType, type EditorContentProps, type EditorMode, type EditorRootProps, FRONTMATTER, FormatButtons, FrontmatterNode, HistoryButtons, InsertButtons, MarkdownEditor, type MarkdownEditorApi, type Props as MarkdownEditorProps, type SerializedDrawingNode, type SerializedFrontmatterNode, TABLE, TABLE_WIDTH_MARKER, Toolbar, ToolbarButton, ToolbarDivider, type ToolbarItems, parseDrawingData, serializeDrawingData, useMarkdownEditor };
445
+ export { $createDrawingNode, $createFrontmatterNode, $getSelectedTable, $getTableDensity, $getTableSettings, $getTableWidth, $isDrawingNode, $isFrontmatterNode, $setTableDensity, $setTableSettings, $setTableWidth, type BlockWidth, DEFAULT_TABLE_SETTINGS, DRAWING, DRAWING_DATA_JSON_SCHEMA, type DrawingData, DrawingNode, type DrawingShape, type DrawingShapeType, type EditorContentProps, type EditorMode, type EditorRootProps, FRONTMATTER, FormatButtons, FrontmatterNode, HistoryButtons, InsertButtons, MarkdownEditor, type MarkdownEditorApi, type Props as MarkdownEditorProps, type SerializedDrawingNode, type SerializedFrontmatterNode, TABLE, TABLE_WIDTH_MARKER, type TableDensity, type TableSettings, Toolbar, ToolbarButton, ToolbarDivider, type ToolbarItems, formatTableSettingsMarker, parseDrawingData, parseTableSettingsMarker, serializeDrawingData, useMarkdownEditor };
package/dist/index.js CHANGED
@@ -2046,7 +2046,7 @@ import {
2046
2046
  TableRowNode
2047
2047
  } from "@lexical/table";
2048
2048
 
2049
- // src/transformers/tableWidth.ts
2049
+ // src/transformers/tableSettings.ts
2050
2050
  import {
2051
2051
  $getNodeByKey as $getNodeByKey3,
2052
2052
  $getSelection as $getSelection2,
@@ -2054,15 +2054,89 @@ import {
2054
2054
  } from "lexical";
2055
2055
  import { $findMatchingParent } from "@lexical/utils";
2056
2056
  import { $isTableNode, $isTableSelection } from "@lexical/table";
2057
- var TABLE_WIDTH_MARKER = "<!-- width: content -->";
2058
- var TABLE_WIDTH_MARKER_REG_EXP = /^<!--\s*width:\s*content\s*-->\s*$/;
2059
- var CONTENT_WIDTH_STYLE = "width: auto; table-layout: auto";
2060
- var CONTENT_WIDTH_REG_EXP = /(^|;)\s*width:\s*auto\s*(;|$)/;
2057
+ var DEFAULT_TABLE_SETTINGS = {
2058
+ width: "full",
2059
+ density: "comfortable"
2060
+ };
2061
+ var OPTION_DECLARATIONS = {
2062
+ width: {
2063
+ full: {},
2064
+ content: { width: "auto", "table-layout": "auto" }
2065
+ },
2066
+ density: {
2067
+ comfortable: {},
2068
+ compact: {
2069
+ "--zui-table-cell-padding": "0.25rem 0.5rem",
2070
+ "--zui-table-font-size": "0.8125rem"
2071
+ },
2072
+ spacious: {
2073
+ "--zui-table-cell-padding": "0.875rem 1rem",
2074
+ "--zui-table-font-size": "0.9375rem"
2075
+ }
2076
+ }
2077
+ };
2078
+ var SETTING_KEYS = Object.keys(OPTION_DECLARATIONS);
2079
+ function parseStyle(style) {
2080
+ const declarations = /* @__PURE__ */ new Map();
2081
+ for (const part of style.split(";")) {
2082
+ const index = part.indexOf(":");
2083
+ if (index === -1) continue;
2084
+ const key = part.slice(0, index).trim();
2085
+ const value = part.slice(index + 1).trim();
2086
+ if (key && value) declarations.set(key, value);
2087
+ }
2088
+ return declarations;
2089
+ }
2090
+ function serializeStyle(declarations) {
2091
+ return [...declarations].map(([key, value]) => `${key}: ${value}`).join("; ");
2092
+ }
2093
+ function optionsOf(setting) {
2094
+ return Object.entries(OPTION_DECLARATIONS[setting]);
2095
+ }
2096
+ function readSetting(declarations, setting) {
2097
+ for (const [option, decls] of optionsOf(setting)) {
2098
+ const entries = Object.entries(decls);
2099
+ if (entries.length === 0) continue;
2100
+ if (entries.every(([key, value]) => declarations.get(key) === value)) {
2101
+ return option;
2102
+ }
2103
+ }
2104
+ return DEFAULT_TABLE_SETTINGS[setting];
2105
+ }
2106
+ function writeSetting(declarations, setting, option) {
2107
+ for (const [, decls] of optionsOf(setting)) {
2108
+ for (const key of Object.keys(decls)) declarations.delete(key);
2109
+ }
2110
+ for (const [key, value] of Object.entries(OPTION_DECLARATIONS[setting][option])) {
2111
+ declarations.set(key, value);
2112
+ }
2113
+ }
2114
+ function $getTableSettings(table) {
2115
+ const declarations = parseStyle(table.getStyle());
2116
+ return {
2117
+ width: readSetting(declarations, "width"),
2118
+ density: readSetting(declarations, "density")
2119
+ };
2120
+ }
2121
+ function $setTableSettings(table, settings) {
2122
+ const declarations = parseStyle(table.getStyle());
2123
+ for (const key of SETTING_KEYS) {
2124
+ const option = settings[key];
2125
+ if (option !== void 0) writeSetting(declarations, key, option);
2126
+ }
2127
+ table.setStyle(serializeStyle(declarations));
2128
+ }
2061
2129
  function $getTableWidth(table) {
2062
- return CONTENT_WIDTH_REG_EXP.test(table.getStyle()) ? "content" : "full";
2130
+ return $getTableSettings(table).width;
2063
2131
  }
2064
2132
  function $setTableWidth(table, width) {
2065
- table.setStyle(width === "content" ? CONTENT_WIDTH_STYLE : "");
2133
+ $setTableSettings(table, { width });
2134
+ }
2135
+ function $getTableDensity(table) {
2136
+ return $getTableSettings(table).density;
2137
+ }
2138
+ function $setTableDensity(table, density) {
2139
+ $setTableSettings(table, { density });
2066
2140
  }
2067
2141
  function $getSelectedTable() {
2068
2142
  const selection = $getSelection2();
@@ -2077,14 +2151,36 @@ function $getSelectedTable() {
2077
2151
  );
2078
2152
  return $isTableNode(table) ? table : null;
2079
2153
  }
2154
+ var MARKER_REG_EXP = /^<!--\s*([a-z]+\s*:\s*[a-z]+(?:\s*;\s*[a-z]+\s*:\s*[a-z]+)*)\s*;?\s*-->\s*$/;
2155
+ function isOption(setting, value) {
2156
+ return value in OPTION_DECLARATIONS[setting];
2157
+ }
2158
+ var TABLE_WIDTH_MARKER = "<!-- width: content -->";
2159
+ function parseTableSettingsMarker(text) {
2160
+ const match = MARKER_REG_EXP.exec(text);
2161
+ if (!match) return null;
2162
+ const settings = {};
2163
+ for (const pair of match[1].split(";")) {
2164
+ const [key, value] = pair.split(":").map((s) => s.trim());
2165
+ if (key === "width" && isOption("width", value)) settings.width = value;
2166
+ if (key === "density" && isOption("density", value)) settings.density = value;
2167
+ }
2168
+ return Object.keys(settings).length > 0 ? settings : null;
2169
+ }
2170
+ function formatTableSettingsMarker(settings) {
2171
+ const pairs = SETTING_KEYS.filter(
2172
+ (key) => settings[key] !== DEFAULT_TABLE_SETTINGS[key]
2173
+ ).map((key) => `${key}: ${settings[key]}`);
2174
+ return pairs.length > 0 ? `<!-- ${pairs.join("; ")} -->` : null;
2175
+ }
2080
2176
 
2081
2177
  // src/transformers/tableTransformer.ts
2082
2178
  var TABLE_ROW_REG_EXP = /^\|(.+)\|\s?$/;
2083
2179
  var TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/;
2084
- function $isTableWidthMarker(node) {
2085
- if (!$isParagraphNode2(node) || node.getChildrenSize() !== 1) return false;
2180
+ function $getMarkerSettings(node) {
2181
+ if (!$isParagraphNode2(node) || node.getChildrenSize() !== 1) return null;
2086
2182
  const child = node.getFirstChild();
2087
- return $isTextNode(child) && TABLE_WIDTH_MARKER_REG_EXP.test(child.getTextContent());
2183
+ return $isTextNode(child) ? parseTableSettingsMarker(child.getTextContent()) : null;
2088
2184
  }
2089
2185
  function getTableColumnsSize(table) {
2090
2186
  const row = table.getFirstChild();
@@ -2106,7 +2202,8 @@ var TABLE = {
2106
2202
  export: (node) => {
2107
2203
  if (!$isTableNode2(node)) return null;
2108
2204
  const output = [];
2109
- if ($getTableWidth(node) === "content") output.push(TABLE_WIDTH_MARKER);
2205
+ const marker = formatTableSettingsMarker($getTableSettings(node));
2206
+ if (marker) output.push(marker);
2110
2207
  for (const row of node.getChildren()) {
2111
2208
  if (!$isTableRowNode(row)) continue;
2112
2209
  const rowOutput = [];
@@ -2172,8 +2269,9 @@ var TABLE = {
2172
2269
  previousSibling.append(...table.getChildren());
2173
2270
  parentNode.remove();
2174
2271
  } else {
2175
- if ($isTableWidthMarker(previousSibling)) {
2176
- $setTableWidth(table, "content");
2272
+ const markerSettings = $getMarkerSettings(previousSibling);
2273
+ if (markerSettings) {
2274
+ $setTableSettings(table, markerSettings);
2177
2275
  previousSibling?.remove();
2178
2276
  }
2179
2277
  parentNode.replace(table);
@@ -2521,7 +2619,7 @@ function FoldingPlugin() {
2521
2619
  )) });
2522
2620
  }
2523
2621
 
2524
- // src/plugins/TableWidthPlugin.tsx
2622
+ // src/plugins/TableSettingsPlugin.tsx
2525
2623
  import { useCallback as useCallback5, useEffect as useEffect9, useState as useState5 } from "react";
2526
2624
  import { useLexicalComposerContext as useLexicalComposerContext9 } from "@lexical/react/LexicalComposerContext";
2527
2625
  import { useLexicalEditable as useLexicalEditable2 } from "@lexical/react/useLexicalEditable";
@@ -2555,13 +2653,13 @@ function useMarkdownEditor() {
2555
2653
  );
2556
2654
  const [canUndo, setCanUndo] = useState4(false);
2557
2655
  const [canRedo, setCanRedo] = useState4(false);
2558
- const [tableWidth, setTableWidthState] = useState4(null);
2656
+ const [tableSettings, setTableSettingsState] = useState4(null);
2559
2657
  useEffect8(
2560
2658
  () => mergeRegister(
2561
2659
  editor.registerUpdateListener(({ editorState }) => {
2562
2660
  editorState.read(() => {
2563
2661
  const table = $getSelectedTable();
2564
- setTableWidthState(table ? $getTableWidth(table) : null);
2662
+ setTableSettingsState(table ? $getTableSettings(table) : null);
2565
2663
  const selection = $getSelection4();
2566
2664
  if (!$isRangeSelection4(selection)) {
2567
2665
  setActiveFormats(/* @__PURE__ */ new Set());
@@ -2614,15 +2712,23 @@ function useMarkdownEditor() {
2614
2712
  $insertNodeToNearestRoot($createDrawingNode());
2615
2713
  });
2616
2714
  }, [editor]);
2617
- const setTableWidth = useCallback4(
2618
- (width) => {
2715
+ const updateTableSettings = useCallback4(
2716
+ (settings) => {
2619
2717
  editor.update(() => {
2620
2718
  const table = $getSelectedTable();
2621
- if (table) $setTableWidth(table, width);
2719
+ if (table) $setTableSettings(table, settings);
2622
2720
  });
2623
2721
  },
2624
2722
  [editor]
2625
2723
  );
2724
+ const setTableWidth = useCallback4(
2725
+ (width) => updateTableSettings({ width }),
2726
+ [updateTableSettings]
2727
+ );
2728
+ const setTableDensity = useCallback4(
2729
+ (density) => updateTableSettings({ density }),
2730
+ [updateTableSettings]
2731
+ );
2626
2732
  const undo = useCallback4(() => {
2627
2733
  editor.dispatchCommand(UNDO_COMMAND, void 0);
2628
2734
  }, [editor]);
@@ -2635,8 +2741,10 @@ function useMarkdownEditor() {
2635
2741
  toggleFormat,
2636
2742
  insertTable,
2637
2743
  insertDrawing,
2638
- tableWidth,
2744
+ tableWidth: tableSettings?.width ?? null,
2639
2745
  setTableWidth,
2746
+ tableDensity: tableSettings?.density ?? null,
2747
+ setTableDensity,
2640
2748
  canUndo,
2641
2749
  canRedo,
2642
2750
  undo,
@@ -2765,12 +2873,45 @@ function HistoryButtons() {
2765
2873
  ] });
2766
2874
  }
2767
2875
 
2768
- // src/plugins/TableWidthPlugin.tsx
2769
- import { jsx as jsx7 } from "react/jsx-runtime";
2770
- function TableWidthPlugin() {
2876
+ // src/plugins/TableSettingsPlugin.tsx
2877
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
2878
+ var DENSITY_OPTIONS = [
2879
+ {
2880
+ density: "compact",
2881
+ label: "Compact rows",
2882
+ icon: /* @__PURE__ */ jsx7("path", { d: "M3 4.5h14M3 8.17h14M3 11.83h14M3 15.5h14" })
2883
+ },
2884
+ {
2885
+ density: "comfortable",
2886
+ label: "Comfortable rows",
2887
+ icon: /* @__PURE__ */ jsx7("path", { d: "M3 4.5h14M3 10h14M3 15.5h14" })
2888
+ },
2889
+ {
2890
+ density: "spacious",
2891
+ label: "Spacious rows",
2892
+ icon: /* @__PURE__ */ jsx7("path", { d: "M3 5h14M3 15h14" })
2893
+ }
2894
+ ];
2895
+ function Icon2({ children }) {
2896
+ return /* @__PURE__ */ jsx7(
2897
+ "svg",
2898
+ {
2899
+ viewBox: "0 0 20 20",
2900
+ width: "16",
2901
+ height: "16",
2902
+ fill: "none",
2903
+ stroke: "currentColor",
2904
+ strokeWidth: "1.6",
2905
+ strokeLinecap: "round",
2906
+ strokeLinejoin: "round",
2907
+ children
2908
+ }
2909
+ );
2910
+ }
2911
+ function TableSettingsPlugin() {
2771
2912
  const [editor] = useLexicalComposerContext9();
2772
2913
  const isEditable = useLexicalEditable2();
2773
- const { tableWidth, setTableWidth } = useMarkdownEditor();
2914
+ const { tableWidth, setTableWidth, tableDensity, setTableDensity } = useMarkdownEditor();
2774
2915
  const [anchor, setAnchor] = useState5(null);
2775
2916
  const sync = useCallback5(() => {
2776
2917
  const tableKey = editor.getEditorState().read(() => $getSelectedTable()?.getKey());
@@ -2798,43 +2939,43 @@ function TableWidthPlugin() {
2798
2939
  observer?.disconnect();
2799
2940
  };
2800
2941
  }, [editor, sync]);
2801
- if (!isEditable || !anchor || !tableWidth) return null;
2802
- return /* @__PURE__ */ jsx7(
2942
+ if (!isEditable || !anchor || !tableWidth || !tableDensity) return null;
2943
+ return /* @__PURE__ */ jsxs5(
2803
2944
  "div",
2804
2945
  {
2805
- className: "zui-table-width",
2806
- role: "group",
2807
- "aria-label": "Table width",
2946
+ className: "zui-table-settings",
2947
+ role: "toolbar",
2948
+ "aria-label": "Table settings",
2808
2949
  style: { top: anchor.top, right: anchor.right },
2809
- children: BLOCK_WIDTH_OPTIONS.map(({ width, label, icon }) => /* @__PURE__ */ jsx7(
2810
- ToolbarButton,
2811
- {
2812
- label,
2813
- active: tableWidth === width,
2814
- onClick: () => setTableWidth(width),
2815
- children: /* @__PURE__ */ jsx7(
2816
- "svg",
2817
- {
2818
- viewBox: "0 0 20 20",
2819
- width: "16",
2820
- height: "16",
2821
- fill: "none",
2822
- stroke: "currentColor",
2823
- strokeWidth: "1.6",
2824
- strokeLinecap: "round",
2825
- strokeLinejoin: "round",
2826
- children: icon
2827
- }
2828
- )
2829
- },
2830
- width
2831
- ))
2950
+ children: [
2951
+ BLOCK_WIDTH_OPTIONS.map(({ width, label, icon }) => /* @__PURE__ */ jsx7(
2952
+ ToolbarButton,
2953
+ {
2954
+ label,
2955
+ active: tableWidth === width,
2956
+ onClick: () => setTableWidth(width),
2957
+ children: /* @__PURE__ */ jsx7(Icon2, { children: icon })
2958
+ },
2959
+ width
2960
+ )),
2961
+ /* @__PURE__ */ jsx7(ToolbarDivider, {}),
2962
+ DENSITY_OPTIONS.map(({ density, label, icon }) => /* @__PURE__ */ jsx7(
2963
+ ToolbarButton,
2964
+ {
2965
+ label,
2966
+ active: tableDensity === density,
2967
+ onClick: () => setTableDensity(density),
2968
+ children: /* @__PURE__ */ jsx7(Icon2, { children: icon })
2969
+ },
2970
+ density
2971
+ ))
2972
+ ]
2832
2973
  }
2833
2974
  );
2834
2975
  }
2835
2976
 
2836
2977
  // src/EditorContent.tsx
2837
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
2978
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2838
2979
  function EditorContent({
2839
2980
  placeholder = "Start writing...",
2840
2981
  foldable = true,
@@ -2855,8 +2996,8 @@ function EditorContent({
2855
2996
  }
2856
2997
  );
2857
2998
  }
2858
- return /* @__PURE__ */ jsxs5("div", { className: "zui-text-editor-body", children: [
2859
- /* @__PURE__ */ jsxs5("div", { className: "zui-text-editor-main", children: [
2999
+ return /* @__PURE__ */ jsxs6("div", { className: "zui-text-editor-body", children: [
3000
+ /* @__PURE__ */ jsxs6("div", { className: "zui-text-editor-main", children: [
2860
3001
  /* @__PURE__ */ jsx8(
2861
3002
  RichTextPlugin,
2862
3003
  {
@@ -2872,7 +3013,7 @@ function EditorContent({
2872
3013
  }
2873
3014
  ),
2874
3015
  foldable && /* @__PURE__ */ jsx8(FoldingPlugin, {}),
2875
- /* @__PURE__ */ jsx8(TableWidthPlugin, {})
3016
+ /* @__PURE__ */ jsx8(TableSettingsPlugin, {})
2876
3017
  ] }),
2877
3018
  children
2878
3019
  ] });
@@ -2884,7 +3025,7 @@ import { useLexicalComposerContext as useLexicalComposerContext10 } from "@lexic
2884
3025
  import {
2885
3026
  TableOfContentsPlugin
2886
3027
  } from "@lexical/react/LexicalTableOfContentsPlugin";
2887
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
3028
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2888
3029
  var INDENT_PER_LEVEL = {
2889
3030
  h1: 0,
2890
3031
  h2: 1,
@@ -2940,7 +3081,7 @@ function OutlinePlugin() {
2940
3081
  for (const [key] of entries) {
2941
3082
  editor.getElementByKey(key)?.setAttribute("data-outline-key", key);
2942
3083
  }
2943
- return /* @__PURE__ */ jsxs6(
3084
+ return /* @__PURE__ */ jsxs7(
2944
3085
  "div",
2945
3086
  {
2946
3087
  className: `zui-text-editor-outline ${collapsed ? "is-collapsed" : ""}`,
@@ -2969,7 +3110,7 @@ function OutlinePlugin() {
2969
3110
  )
2970
3111
  }
2971
3112
  ),
2972
- !collapsed && /* @__PURE__ */ jsxs6("nav", { className: "zui-text-editor-outline-list", "aria-label": "Table of contents", children: [
3113
+ !collapsed && /* @__PURE__ */ jsxs7("nav", { className: "zui-text-editor-outline-list", "aria-label": "Table of contents", children: [
2973
3114
  entries.length === 0 && /* @__PURE__ */ jsx9("div", { className: "zui-text-editor-outline-empty", children: "No headings" }),
2974
3115
  entries.map(([key, text, tag]) => /* @__PURE__ */ jsx9(
2975
3116
  "button",
@@ -2992,7 +3133,7 @@ function OutlinePlugin() {
2992
3133
  }
2993
3134
 
2994
3135
  // src/MarkdownEditor.tsx
2995
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
3136
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2996
3137
  var DEFAULT_ITEMS = {
2997
3138
  format: /* @__PURE__ */ jsx10(FormatButtons, {}),
2998
3139
  insert: /* @__PURE__ */ jsx10(InsertButtons, {}),
@@ -3013,7 +3154,7 @@ function MarkdownEditor({
3013
3154
  foldable = true,
3014
3155
  ...rootProps
3015
3156
  }) {
3016
- return /* @__PURE__ */ jsxs7(EditorRoot, { ...rootProps, children: [
3157
+ return /* @__PURE__ */ jsxs8(EditorRoot, { ...rootProps, children: [
3017
3158
  toolbar === true && /* @__PURE__ */ jsx10(EditorToolbar, {}),
3018
3159
  typeof toolbar === "function" && toolbar(DEFAULT_ITEMS),
3019
3160
  /* @__PURE__ */ jsx10(EditorContent, { placeholder, foldable, children: outline && /* @__PURE__ */ jsx10(OutlinePlugin, {}) })
@@ -3147,10 +3288,15 @@ export {
3147
3288
  $createDrawingNode,
3148
3289
  $createFrontmatterNode,
3149
3290
  $getSelectedTable,
3291
+ $getTableDensity,
3292
+ $getTableSettings,
3150
3293
  $getTableWidth,
3151
3294
  $isDrawingNode,
3152
3295
  $isFrontmatterNode,
3296
+ $setTableDensity,
3297
+ $setTableSettings,
3153
3298
  $setTableWidth,
3299
+ DEFAULT_TABLE_SETTINGS,
3154
3300
  DRAWING,
3155
3301
  DRAWING_DATA_JSON_SCHEMA,
3156
3302
  DrawingNode,
@@ -3165,7 +3311,9 @@ export {
3165
3311
  Toolbar,
3166
3312
  ToolbarButton,
3167
3313
  ToolbarDivider,
3314
+ formatTableSettingsMarker,
3168
3315
  parseDrawingData,
3316
+ parseTableSettingsMarker,
3169
3317
  serializeDrawingData,
3170
3318
  useMarkdownEditor
3171
3319
  };
package/dist/styles.css CHANGED
@@ -282,12 +282,13 @@
282
282
  border: 1px solid color-mix(in srgb, currentColor 15%, transparent);
283
283
  border-radius: 0.625rem;
284
284
  overflow: hidden;
285
- font-size: 0.875rem;
285
+ /* Density presets set these custom properties inline (tableSettings.ts) */
286
+ font-size: var(--zui-table-font-size, 0.875rem);
286
287
  table-layout: fixed;
287
288
  }
288
289
 
289
290
  .zui-table-cell {
290
- padding: 0.5rem 0.75rem;
291
+ padding: var(--zui-table-cell-padding, 0.5rem 0.75rem);
291
292
  border-bottom: 1px solid color-mix(in srgb, currentColor 10%, transparent);
292
293
  border-right: 1px solid color-mix(in srgb, currentColor 10%, transparent);
293
294
  vertical-align: top;
@@ -319,13 +320,13 @@
319
320
  }
320
321
 
321
322
  /*
322
- * Floating full-width / content-width toggle, straddling the top-right corner
323
+ * Floating table settings (width, density), straddling the top-right corner
323
324
  * of the table the caret is in (positioned within .zui-text-editor-main). It
324
325
  * stays inside the table's own top margin so it never covers the previous
325
- * paragraph. Content width itself is an inline style on the <table> (see
326
- * tableWidth.ts).
326
+ * paragraph. The settings themselves are an inline style on the <table>
327
+ * (see tableSettings.ts).
327
328
  */
328
- .zui-table-width {
329
+ .zui-table-settings {
329
330
  position: absolute;
330
331
  z-index: 5;
331
332
  display: flex;
@@ -338,7 +339,7 @@
338
339
  transform: translateY(-70%);
339
340
  }
340
341
 
341
- .dark .zui-table-width {
342
+ .dark .zui-table-settings {
342
343
  background: #232329;
343
344
  border-color: rgba(255, 255, 255, 0.1);
344
345
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuilib/text-editor",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "ZUI — A markdown editor component wrapping Lexical",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",