quoin-editor 0.1.1 → 0.3.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/index.js CHANGED
@@ -52,12 +52,12 @@ function blockByType(definitions, type) {
52
52
  return void 0;
53
53
  }
54
54
  function paletteBlocks(definitions) {
55
- const seen = /* @__PURE__ */ new Set();
55
+ const seen2 = /* @__PURE__ */ new Set();
56
56
  const out = [];
57
57
  for (let i = definitions.length - 1; i >= 0; i -= 1) {
58
58
  const d = definitions[i];
59
- if (seen.has(d.type)) continue;
60
- seen.add(d.type);
59
+ if (seen2.has(d.type)) continue;
60
+ seen2.add(d.type);
61
61
  if (d.inSlashPalette === false) continue;
62
62
  out.push(d);
63
63
  }
@@ -143,12 +143,7 @@ function Button({
143
143
 
144
144
  // src/upload/FilePickerButton.tsx
145
145
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
146
- function FilePickerButton({
147
- label,
148
- accept,
149
- disabled,
150
- onPick
151
- }) {
146
+ function FilePickerButton({ label, accept, disabled, onPick }) {
152
147
  return /* @__PURE__ */ jsxs("span", { className: "relative inline-flex rounded-md has-[:focus-visible]:ring-[3px] has-[:focus-visible]:ring-ring/50", children: [
153
148
  /* @__PURE__ */ jsx3(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: disabled ? "pointer-events-none opacity-50" : void 0, children: [
154
149
  /* @__PURE__ */ jsx3(Upload, { "aria-hidden": "true" }),
@@ -1366,12 +1361,7 @@ import { useEffect as useEffect2, useRef as useRef3 } from "react";
1366
1361
  // src/upload/UploadField.tsx
1367
1362
  import * as React6 from "react";
1368
1363
  import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1369
- function UploadField({
1370
- category,
1371
- label,
1372
- accept,
1373
- onUploaded
1374
- }) {
1364
+ function UploadField({ category, label, accept, onUploaded }) {
1375
1365
  const { upload, onError } = useQuoin();
1376
1366
  const [busy, setBusy] = React6.useState(false);
1377
1367
  const [error, setError] = React6.useState(null);
@@ -1835,9 +1825,8 @@ function BlockEditor({
1835
1825
  onDone
1836
1826
  }) {
1837
1827
  const { blockTypes } = useQuoin();
1838
- const definition = blockByType(blockTypes, type);
1839
- if (!definition) return null;
1840
- const Editor = definition.editor;
1828
+ const Editor = blockByType(blockTypes, type)?.editor;
1829
+ if (!Editor) return null;
1841
1830
  return /* @__PURE__ */ jsx21(Editor, { data, onChange, onInsertBlock, onDone });
1842
1831
  }
1843
1832
 
@@ -1886,6 +1875,15 @@ function sourceOffsetFor(source, probe) {
1886
1875
  return search(source, probe);
1887
1876
  }
1888
1877
 
1878
+ // src/dev-warn.ts
1879
+ var seen = /* @__PURE__ */ new Set();
1880
+ function devWarn(message) {
1881
+ if (typeof process !== "undefined" && process?.env?.NODE_ENV === "production") return;
1882
+ if (seen.has(message)) return;
1883
+ seen.add(message);
1884
+ console.warn(`Quoin: ${message}`);
1885
+ }
1886
+
1889
1887
  // src/canvas/BlockCard.tsx
1890
1888
  import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
1891
1889
  var INTERACTIVE_SELECTOR = 'a, button, input, textarea, select, [role="button"], [tabindex], iframe';
@@ -1907,7 +1905,8 @@ function BlockCardImpl({
1907
1905
  const { blockTypes, onError } = useQuoin();
1908
1906
  const definition = blockByType(blockTypes, block.type);
1909
1907
  const Preview = definition?.preview;
1910
- const [editing, setEditing] = React8.useState(editingByDefault && !readOnly);
1908
+ const editable = Boolean(definition?.editor) && !readOnly;
1909
+ const [editing, setEditing] = React8.useState(editingByDefault && editable);
1911
1910
  const editButtonRef = React8.useRef(null);
1912
1911
  const editorContainerRef = React8.useRef(null);
1913
1912
  const returnFocusOnClose = React8.useRef(false);
@@ -1987,7 +1986,13 @@ function BlockCardImpl({
1987
1986
  reportedType.current = block.type;
1988
1987
  onError(new Error(`No block type is registered for "${block.type}".`));
1989
1988
  }, [definition, block.type, onError]);
1990
- if (definition?.alwaysEditing && !readOnly) {
1989
+ React8.useEffect(() => {
1990
+ if (!readOnly || !definition || definition.preview) return;
1991
+ devWarn(
1992
+ `the block type "${block.type}" has no \`preview\`, so a read-only canvas can draw only its label. Give its definition a \`preview\` to render the block itself.`
1993
+ );
1994
+ }, [readOnly, definition, block.type]);
1995
+ if (definition?.alwaysEditing && editable) {
1991
1996
  return /* @__PURE__ */ jsx22(
1992
1997
  BlockEditor,
1993
1998
  {
@@ -2027,7 +2032,7 @@ function BlockCardImpl({
2027
2032
  );
2028
2033
  }
2029
2034
  const rest = Preview ? /* @__PURE__ */ jsx22(Preview, { data: block.data, blockId: block.id }) : /* @__PURE__ */ jsx22("p", { className: "py-1 text-sm text-muted-foreground", children: definition?.label ?? block.type });
2030
- if (readOnly) return /* @__PURE__ */ jsx22("div", { children: rest });
2035
+ if (!editable) return /* @__PURE__ */ jsx22("div", { children: rest });
2031
2036
  return /* @__PURE__ */ jsxs15("div", { className: "group/card relative", children: [
2032
2037
  /* @__PURE__ */ jsx22(
2033
2038
  "div",
@@ -2189,6 +2194,16 @@ function DocumentStarter({
2189
2194
  ] });
2190
2195
  }
2191
2196
 
2197
+ // src/canvas/ReadOnlyCanvas.tsx
2198
+ import { jsx as jsx25 } from "react/jsx-runtime";
2199
+ function ReadOnlyCanvas({
2200
+ blocks,
2201
+ onUpdate,
2202
+ root
2203
+ }) {
2204
+ return /* @__PURE__ */ jsx25("div", { ...root, children: /* @__PURE__ */ jsx25("ul", { className: "flex flex-col", children: blocks.map((b, i) => /* @__PURE__ */ jsx25("li", { children: /* @__PURE__ */ jsx25("div", { "data-block-index": i, className: "rounded-lg px-2 py-1", children: /* @__PURE__ */ jsx25(BlockCard, { block: b, index: i, editingByDefault: false, readOnly: true, onUpdate }) }) }, b.id)) }) });
2205
+ }
2206
+
2192
2207
  // src/canvas/reorder-indices.ts
2193
2208
  function reorderIndices(ids, activeId, overId) {
2194
2209
  if (overId === null || activeId === overId) return null;
@@ -2218,7 +2233,7 @@ function sameBlocks(a, b) {
2218
2233
  import { useSortable } from "@dnd-kit/sortable";
2219
2234
  import { CSS } from "@dnd-kit/utilities";
2220
2235
  import { GripVertical } from "lucide-react";
2221
- import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
2236
+ import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
2222
2237
  var GUTTER = [
2223
2238
  "flex items-center justify-end gap-0.5 pb-1",
2224
2239
  "lg:absolute lg:right-full lg:top-1 lg:justify-start lg:pb-0 lg:pr-1",
@@ -2241,7 +2256,7 @@ function SortableBlock({
2241
2256
  children: [
2242
2257
  /* @__PURE__ */ jsxs18("div", { className: GUTTER, children: [
2243
2258
  actions,
2244
- /* @__PURE__ */ jsx25(
2259
+ /* @__PURE__ */ jsx26(
2245
2260
  Button,
2246
2261
  {
2247
2262
  type: "button",
@@ -2252,17 +2267,17 @@ function SortableBlock({
2252
2267
  "aria-label": `Reorder block ${index + 1}`,
2253
2268
  ...attributes,
2254
2269
  ...listeners,
2255
- children: /* @__PURE__ */ jsx25(GripVertical, { "aria-hidden": "true" })
2270
+ children: /* @__PURE__ */ jsx26(GripVertical, { "aria-hidden": "true" })
2256
2271
  }
2257
2272
  )
2258
2273
  ] }),
2259
- /* @__PURE__ */ jsx25(
2274
+ /* @__PURE__ */ jsx26(
2260
2275
  "div",
2261
2276
  {
2262
2277
  "data-block-index": index,
2263
2278
  className: cn(
2264
2279
  "rounded-lg px-2 py-1 transition-colors",
2265
- isDragging ? "bg-card shadow-[var(--shadow-md)]" : "group-hover/block:bg-muted/40"
2280
+ isDragging ? "bg-popover shadow-md" : "group-hover/block:bg-muted/40"
2266
2281
  ),
2267
2282
  children
2268
2283
  }
@@ -2272,8 +2287,7 @@ function SortableBlock({
2272
2287
  );
2273
2288
  }
2274
2289
 
2275
- // src/canvas/BlockCanvas.tsx
2276
- import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
2290
+ // src/canvas/unacknowledged.ts
2277
2291
  var UNACKNOWLEDGED_LIMIT = 64;
2278
2292
  function remember(outstanding, blocks) {
2279
2293
  outstanding.add(blocks);
@@ -2283,9 +2297,13 @@ function remember(outstanding, blocks) {
2283
2297
  outstanding.delete(oldest.value);
2284
2298
  }
2285
2299
  }
2300
+
2301
+ // src/canvas/BlockCanvas.tsx
2302
+ import { jsx as jsx27, jsxs as jsxs19 } from "react/jsx-runtime";
2286
2303
  function Canvas({
2287
2304
  blocks,
2288
2305
  onChange,
2306
+ onDelete,
2289
2307
  blockTypes,
2290
2308
  readOnly,
2291
2309
  className,
@@ -2303,6 +2321,7 @@ function Canvas({
2303
2321
  const [seedSelection, setSeedSelection] = React11.useState(null);
2304
2322
  const listRef = React11.useRef(null);
2305
2323
  const pendingDeleteIndexRef = React11.useRef(null);
2324
+ const removedRef = React11.useRef(null);
2306
2325
  const announcements = React11.useMemo(
2307
2326
  () => buildAnnouncements(list.blocks, blockTypes),
2308
2327
  [list.blocks, blockTypes]
@@ -2315,7 +2334,10 @@ function Canvas({
2315
2334
  lastEmitted.current = list.blocks;
2316
2335
  remember(unacknowledged.current, list.blocks);
2317
2336
  onChange(list.blocks);
2318
- }, [list.blocks, onChange]);
2337
+ const removed = removedRef.current;
2338
+ removedRef.current = null;
2339
+ if (removed) onDelete?.(removed.block, removed.index);
2340
+ }, [list.blocks, onChange, onDelete]);
2319
2341
  React11.useEffect(() => {
2320
2342
  const previous = lastSupplied.current;
2321
2343
  lastSupplied.current = blocks;
@@ -2337,7 +2359,10 @@ function Canvas({
2337
2359
  [insertAt, blockTypes]
2338
2360
  );
2339
2361
  function handleDelete(index, id2) {
2362
+ const block = list.blocks[index];
2340
2363
  pendingDeleteIndexRef.current = index;
2364
+ if (block) removedRef.current = { block, index };
2365
+ setNewBlockId((current) => current === id2 ? null : current);
2341
2366
  remove(id2);
2342
2367
  }
2343
2368
  React11.useEffect(() => {
@@ -2364,12 +2389,10 @@ function Canvas({
2364
2389
  role: ariaLabel ? "group" : void 0,
2365
2390
  "aria-label": ariaLabel
2366
2391
  };
2367
- if (readOnly) {
2368
- return /* @__PURE__ */ jsx26("div", { ...rootProps, children: /* @__PURE__ */ jsx26("ul", { className: "flex flex-col", children: list.blocks.map((b, i) => /* @__PURE__ */ jsx26("li", { children: /* @__PURE__ */ jsx26("div", { "data-block-index": i, className: "rounded-lg px-2 py-1", children: /* @__PURE__ */ jsx26(BlockCard, { block: b, index: i, editingByDefault: false, readOnly: true, onUpdate: update }) }) }, b.id)) }) });
2369
- }
2392
+ if (readOnly) return /* @__PURE__ */ jsx27(ReadOnlyCanvas, { blocks: list.blocks, onUpdate: update, root: rootProps });
2370
2393
  return /* @__PURE__ */ jsxs19("div", { ...rootProps, children: [
2371
- /* @__PURE__ */ jsx26(BlockInserter, { index: 0, onInsert: insertPlain, label: "Insert a block at the start" }),
2372
- /* @__PURE__ */ jsx26(
2394
+ /* @__PURE__ */ jsx27(BlockInserter, { index: 0, onInsert: insertPlain, label: "Insert a block at the start" }),
2395
+ /* @__PURE__ */ jsx27(
2373
2396
  DndContext,
2374
2397
  {
2375
2398
  id: dndId,
@@ -2378,13 +2401,13 @@ function Canvas({
2378
2401
  modifiers: [restrictToVerticalAxis],
2379
2402
  accessibility: { announcements },
2380
2403
  onDragEnd: handleDragEnd,
2381
- children: /* @__PURE__ */ jsx26(SortableContext, { items: list.blocks.map((b) => b.id), strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx26("ul", { className: "flex flex-col", children: list.blocks.map((b, i) => /* @__PURE__ */ jsxs19("li", { children: [
2382
- /* @__PURE__ */ jsx26(
2404
+ children: /* @__PURE__ */ jsx27(SortableContext, { items: list.blocks.map((b) => b.id), strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx27("ul", { className: "flex flex-col", children: list.blocks.map((b, i) => /* @__PURE__ */ jsxs19("li", { children: [
2405
+ /* @__PURE__ */ jsx27(
2383
2406
  SortableBlock,
2384
2407
  {
2385
2408
  id: b.id,
2386
2409
  index: i,
2387
- actions: /* @__PURE__ */ jsx26(
2410
+ actions: /* @__PURE__ */ jsx27(
2388
2411
  BlockActionsMenu,
2389
2412
  {
2390
2413
  index: i,
@@ -2394,7 +2417,7 @@ function Canvas({
2394
2417
  onDelete: () => handleDelete(i, b.id)
2395
2418
  }
2396
2419
  ),
2397
- children: /* @__PURE__ */ jsx26(
2420
+ children: /* @__PURE__ */ jsx27(
2398
2421
  BlockCard,
2399
2422
  {
2400
2423
  block: b,
@@ -2407,7 +2430,7 @@ function Canvas({
2407
2430
  )
2408
2431
  }
2409
2432
  ),
2410
- /* @__PURE__ */ jsx26(
2433
+ /* @__PURE__ */ jsx27(
2411
2434
  BlockInserter,
2412
2435
  {
2413
2436
  index: i + 1,
@@ -2418,7 +2441,7 @@ function Canvas({
2418
2441
  ] }, b.id)) }) })
2419
2442
  }
2420
2443
  ),
2421
- /* @__PURE__ */ jsx26(
2444
+ /* @__PURE__ */ jsx27(
2422
2445
  DocumentStarter,
2423
2446
  {
2424
2447
  autoFocus: list.blocks.length === 0,
@@ -2434,6 +2457,7 @@ function Canvas({
2434
2457
  function BlockCanvas({
2435
2458
  blocks,
2436
2459
  onChange,
2460
+ onDelete,
2437
2461
  blockTypes = coreBlocks,
2438
2462
  upload,
2439
2463
  resolveAssetUrl,
@@ -2447,11 +2471,12 @@ function BlockCanvas({
2447
2471
  () => ({ blockTypes, upload, resolveAssetUrl, onError }),
2448
2472
  [blockTypes, upload, resolveAssetUrl, onError]
2449
2473
  );
2450
- return /* @__PURE__ */ jsx26(QuoinProvider, { value: config, children: /* @__PURE__ */ jsx26(
2474
+ return /* @__PURE__ */ jsx27(QuoinProvider, { value: config, children: /* @__PURE__ */ jsx27(
2451
2475
  Canvas,
2452
2476
  {
2453
2477
  blocks,
2454
2478
  onChange,
2479
+ onDelete,
2455
2480
  blockTypes,
2456
2481
  readOnly,
2457
2482
  className,
@@ -2463,6 +2488,9 @@ function BlockCanvas({
2463
2488
 
2464
2489
  // src/hooks/useAutosave.ts
2465
2490
  import * as React12 from "react";
2491
+ function reportedFailure(result) {
2492
+ return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
2493
+ }
2466
2494
  function useAutosave({
2467
2495
  deps,
2468
2496
  build,
@@ -2486,7 +2514,7 @@ function useAutosave({
2486
2514
  setStatus("saving");
2487
2515
  try {
2488
2516
  const res = await saveRef.current(buildRef.current());
2489
- if (res && res.ok === false) throw new Error("save rejected");
2517
+ if (reportedFailure(res)) throw new Error("save rejected");
2490
2518
  retryRef.current = 0;
2491
2519
  dirtyRef.current = false;
2492
2520
  setStatus("saved");
@@ -2502,10 +2530,10 @@ function useAutosave({
2502
2530
  });
2503
2531
  const seenRef = React12.useRef(null);
2504
2532
  React12.useEffect(() => {
2505
- const seen = seenRef.current;
2533
+ const seen2 = seenRef.current;
2506
2534
  seenRef.current = deps;
2507
- if (seen === null) return;
2508
- const unchanged = seen.length === deps.length && seen.every((v, i) => v === deps[i]);
2535
+ if (seen2 === null) return;
2536
+ const unchanged = seen2.length === deps.length && seen2.every((v, i) => v === deps[i]);
2509
2537
  if (!unchanged) dirtyRef.current = true;
2510
2538
  }, deps);
2511
2539
  React12.useEffect(() => {
@@ -2527,8 +2555,10 @@ function useAutosave({
2527
2555
  }
2528
2556
  export {
2529
2557
  BlockCanvas,
2558
+ FilePickerButton,
2530
2559
  QuoinProvider,
2531
2560
  TEXT_COMMANDS,
2561
+ UploadField,
2532
2562
  applyEdit,
2533
2563
  blockByType,
2534
2564
  caretRect,