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.cjs CHANGED
@@ -32,8 +32,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  var src_exports = {};
33
33
  __export(src_exports, {
34
34
  BlockCanvas: () => BlockCanvas,
35
+ FilePickerButton: () => FilePickerButton,
35
36
  QuoinProvider: () => QuoinProvider,
36
37
  TEXT_COMMANDS: () => TEXT_COMMANDS,
38
+ UploadField: () => UploadField,
37
39
  applyEdit: () => applyEdit,
38
40
  blockByType: () => blockByType,
39
41
  caretRect: () => caretRect,
@@ -102,12 +104,12 @@ function blockByType(definitions, type) {
102
104
  return void 0;
103
105
  }
104
106
  function paletteBlocks(definitions) {
105
- const seen = /* @__PURE__ */ new Set();
107
+ const seen2 = /* @__PURE__ */ new Set();
106
108
  const out = [];
107
109
  for (let i = definitions.length - 1; i >= 0; i -= 1) {
108
110
  const d = definitions[i];
109
- if (seen.has(d.type)) continue;
110
- seen.add(d.type);
111
+ if (seen2.has(d.type)) continue;
112
+ seen2.add(d.type);
111
113
  if (d.inSlashPalette === false) continue;
112
114
  out.push(d);
113
115
  }
@@ -193,12 +195,7 @@ function Button({
193
195
 
194
196
  // src/upload/FilePickerButton.tsx
195
197
  var import_jsx_runtime3 = require("react/jsx-runtime");
196
- function FilePickerButton({
197
- label,
198
- accept,
199
- disabled,
200
- onPick
201
- }) {
198
+ function FilePickerButton({ label, accept, disabled, onPick }) {
202
199
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "relative inline-flex rounded-md has-[:focus-visible]:ring-[3px] has-[:focus-visible]:ring-ring/50", children: [
203
200
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { "aria-hidden": "true", className: disabled ? "pointer-events-none opacity-50" : void 0, children: [
204
201
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Upload, { "aria-hidden": "true" }),
@@ -1405,12 +1402,7 @@ var import_react = require("react");
1405
1402
  // src/upload/UploadField.tsx
1406
1403
  var React6 = __toESM(require("react"), 1);
1407
1404
  var import_jsx_runtime15 = require("react/jsx-runtime");
1408
- function UploadField({
1409
- category,
1410
- label,
1411
- accept,
1412
- onUploaded
1413
- }) {
1405
+ function UploadField({ category, label, accept, onUploaded }) {
1414
1406
  const { upload, onError } = useQuoin();
1415
1407
  const [busy, setBusy] = React6.useState(false);
1416
1408
  const [error, setError] = React6.useState(null);
@@ -1874,9 +1866,8 @@ function BlockEditor({
1874
1866
  onDone
1875
1867
  }) {
1876
1868
  const { blockTypes } = useQuoin();
1877
- const definition = blockByType(blockTypes, type);
1878
- if (!definition) return null;
1879
- const Editor = definition.editor;
1869
+ const Editor = blockByType(blockTypes, type)?.editor;
1870
+ if (!Editor) return null;
1880
1871
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Editor, { data, onChange, onInsertBlock, onDone });
1881
1872
  }
1882
1873
 
@@ -1925,6 +1916,15 @@ function sourceOffsetFor(source, probe) {
1925
1916
  return search(source, probe);
1926
1917
  }
1927
1918
 
1919
+ // src/dev-warn.ts
1920
+ var seen = /* @__PURE__ */ new Set();
1921
+ function devWarn(message) {
1922
+ if (typeof process !== "undefined" && process?.env?.NODE_ENV === "production") return;
1923
+ if (seen.has(message)) return;
1924
+ seen.add(message);
1925
+ console.warn(`Quoin: ${message}`);
1926
+ }
1927
+
1928
1928
  // src/canvas/BlockCard.tsx
1929
1929
  var import_jsx_runtime22 = require("react/jsx-runtime");
1930
1930
  var INTERACTIVE_SELECTOR = 'a, button, input, textarea, select, [role="button"], [tabindex], iframe';
@@ -1946,7 +1946,8 @@ function BlockCardImpl({
1946
1946
  const { blockTypes, onError } = useQuoin();
1947
1947
  const definition = blockByType(blockTypes, block.type);
1948
1948
  const Preview = definition?.preview;
1949
- const [editing, setEditing] = React8.useState(editingByDefault && !readOnly);
1949
+ const editable = Boolean(definition?.editor) && !readOnly;
1950
+ const [editing, setEditing] = React8.useState(editingByDefault && editable);
1950
1951
  const editButtonRef = React8.useRef(null);
1951
1952
  const editorContainerRef = React8.useRef(null);
1952
1953
  const returnFocusOnClose = React8.useRef(false);
@@ -2026,7 +2027,13 @@ function BlockCardImpl({
2026
2027
  reportedType.current = block.type;
2027
2028
  onError(new Error(`No block type is registered for "${block.type}".`));
2028
2029
  }, [definition, block.type, onError]);
2029
- if (definition?.alwaysEditing && !readOnly) {
2030
+ React8.useEffect(() => {
2031
+ if (!readOnly || !definition || definition.preview) return;
2032
+ devWarn(
2033
+ `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.`
2034
+ );
2035
+ }, [readOnly, definition, block.type]);
2036
+ if (definition?.alwaysEditing && editable) {
2030
2037
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2031
2038
  BlockEditor,
2032
2039
  {
@@ -2066,7 +2073,7 @@ function BlockCardImpl({
2066
2073
  );
2067
2074
  }
2068
2075
  const rest = Preview ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Preview, { data: block.data, blockId: block.id }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "py-1 text-sm text-muted-foreground", children: definition?.label ?? block.type });
2069
- if (readOnly) return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { children: rest });
2076
+ if (!editable) return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { children: rest });
2070
2077
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "group/card relative", children: [
2071
2078
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2072
2079
  "div",
@@ -2228,6 +2235,16 @@ function DocumentStarter({
2228
2235
  ] });
2229
2236
  }
2230
2237
 
2238
+ // src/canvas/ReadOnlyCanvas.tsx
2239
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2240
+ function ReadOnlyCanvas({
2241
+ blocks,
2242
+ onUpdate,
2243
+ root
2244
+ }) {
2245
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { ...root, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { className: "flex flex-col", children: blocks.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "data-block-index": i, className: "rounded-lg px-2 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(BlockCard, { block: b, index: i, editingByDefault: false, readOnly: true, onUpdate }) }) }, b.id)) }) });
2246
+ }
2247
+
2231
2248
  // src/canvas/reorder-indices.ts
2232
2249
  function reorderIndices(ids, activeId, overId) {
2233
2250
  if (overId === null || activeId === overId) return null;
@@ -2257,7 +2274,7 @@ function sameBlocks(a, b) {
2257
2274
  var import_sortable = require("@dnd-kit/sortable");
2258
2275
  var import_utilities = require("@dnd-kit/utilities");
2259
2276
  var import_lucide_react12 = require("lucide-react");
2260
- var import_jsx_runtime25 = require("react/jsx-runtime");
2277
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2261
2278
  var GUTTER = [
2262
2279
  "flex items-center justify-end gap-0.5 pb-1",
2263
2280
  "lg:absolute lg:right-full lg:top-1 lg:justify-start lg:pb-0 lg:pr-1",
@@ -2271,16 +2288,16 @@ function SortableBlock({
2271
2288
  children
2272
2289
  }) {
2273
2290
  const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = (0, import_sortable.useSortable)({ id });
2274
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2291
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2275
2292
  "div",
2276
2293
  {
2277
2294
  ref: setNodeRef,
2278
2295
  style: { transform: import_utilities.CSS.Transform.toString(transform), transition },
2279
2296
  className: cn("group/block relative", isDragging && "z-10 opacity-80"),
2280
2297
  children: [
2281
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: GUTTER, children: [
2298
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: GUTTER, children: [
2282
2299
  actions,
2283
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2300
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2284
2301
  Button,
2285
2302
  {
2286
2303
  type: "button",
@@ -2291,17 +2308,17 @@ function SortableBlock({
2291
2308
  "aria-label": `Reorder block ${index + 1}`,
2292
2309
  ...attributes,
2293
2310
  ...listeners,
2294
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react12.GripVertical, { "aria-hidden": "true" })
2311
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react12.GripVertical, { "aria-hidden": "true" })
2295
2312
  }
2296
2313
  )
2297
2314
  ] }),
2298
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2315
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2299
2316
  "div",
2300
2317
  {
2301
2318
  "data-block-index": index,
2302
2319
  className: cn(
2303
2320
  "rounded-lg px-2 py-1 transition-colors",
2304
- isDragging ? "bg-card shadow-[var(--shadow-md)]" : "group-hover/block:bg-muted/40"
2321
+ isDragging ? "bg-popover shadow-md" : "group-hover/block:bg-muted/40"
2305
2322
  ),
2306
2323
  children
2307
2324
  }
@@ -2311,8 +2328,7 @@ function SortableBlock({
2311
2328
  );
2312
2329
  }
2313
2330
 
2314
- // src/canvas/BlockCanvas.tsx
2315
- var import_jsx_runtime26 = require("react/jsx-runtime");
2331
+ // src/canvas/unacknowledged.ts
2316
2332
  var UNACKNOWLEDGED_LIMIT = 64;
2317
2333
  function remember(outstanding, blocks) {
2318
2334
  outstanding.add(blocks);
@@ -2322,9 +2338,13 @@ function remember(outstanding, blocks) {
2322
2338
  outstanding.delete(oldest.value);
2323
2339
  }
2324
2340
  }
2341
+
2342
+ // src/canvas/BlockCanvas.tsx
2343
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2325
2344
  function Canvas({
2326
2345
  blocks,
2327
2346
  onChange,
2347
+ onDelete,
2328
2348
  blockTypes,
2329
2349
  readOnly,
2330
2350
  className,
@@ -2342,6 +2362,7 @@ function Canvas({
2342
2362
  const [seedSelection, setSeedSelection] = React11.useState(null);
2343
2363
  const listRef = React11.useRef(null);
2344
2364
  const pendingDeleteIndexRef = React11.useRef(null);
2365
+ const removedRef = React11.useRef(null);
2345
2366
  const announcements = React11.useMemo(
2346
2367
  () => buildAnnouncements(list.blocks, blockTypes),
2347
2368
  [list.blocks, blockTypes]
@@ -2354,7 +2375,10 @@ function Canvas({
2354
2375
  lastEmitted.current = list.blocks;
2355
2376
  remember(unacknowledged.current, list.blocks);
2356
2377
  onChange(list.blocks);
2357
- }, [list.blocks, onChange]);
2378
+ const removed = removedRef.current;
2379
+ removedRef.current = null;
2380
+ if (removed) onDelete?.(removed.block, removed.index);
2381
+ }, [list.blocks, onChange, onDelete]);
2358
2382
  React11.useEffect(() => {
2359
2383
  const previous = lastSupplied.current;
2360
2384
  lastSupplied.current = blocks;
@@ -2376,7 +2400,10 @@ function Canvas({
2376
2400
  [insertAt, blockTypes]
2377
2401
  );
2378
2402
  function handleDelete(index, id2) {
2403
+ const block = list.blocks[index];
2379
2404
  pendingDeleteIndexRef.current = index;
2405
+ if (block) removedRef.current = { block, index };
2406
+ setNewBlockId((current) => current === id2 ? null : current);
2380
2407
  remove(id2);
2381
2408
  }
2382
2409
  React11.useEffect(() => {
@@ -2403,12 +2430,10 @@ function Canvas({
2403
2430
  role: ariaLabel ? "group" : void 0,
2404
2431
  "aria-label": ariaLabel
2405
2432
  };
2406
- if (readOnly) {
2407
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { ...rootProps, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "flex flex-col", children: list.blocks.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { "data-block-index": i, className: "rounded-lg px-2 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(BlockCard, { block: b, index: i, editingByDefault: false, readOnly: true, onUpdate: update }) }) }, b.id)) }) });
2408
- }
2409
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { ...rootProps, children: [
2410
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(BlockInserter, { index: 0, onInsert: insertPlain, label: "Insert a block at the start" }),
2411
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2433
+ if (readOnly) return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ReadOnlyCanvas, { blocks: list.blocks, onUpdate: update, root: rootProps });
2434
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { ...rootProps, children: [
2435
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(BlockInserter, { index: 0, onInsert: insertPlain, label: "Insert a block at the start" }),
2436
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2412
2437
  import_core.DndContext,
2413
2438
  {
2414
2439
  id: dndId,
@@ -2417,13 +2442,13 @@ function Canvas({
2417
2442
  modifiers: [import_modifiers.restrictToVerticalAxis],
2418
2443
  accessibility: { announcements },
2419
2444
  onDragEnd: handleDragEnd,
2420
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_sortable2.SortableContext, { items: list.blocks.map((b) => b.id), strategy: import_sortable2.verticalListSortingStrategy, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "flex flex-col", children: list.blocks.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("li", { children: [
2421
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2445
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_sortable2.SortableContext, { items: list.blocks.map((b) => b.id), strategy: import_sortable2.verticalListSortingStrategy, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("ul", { className: "flex flex-col", children: list.blocks.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("li", { children: [
2446
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2422
2447
  SortableBlock,
2423
2448
  {
2424
2449
  id: b.id,
2425
2450
  index: i,
2426
- actions: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2451
+ actions: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2427
2452
  BlockActionsMenu,
2428
2453
  {
2429
2454
  index: i,
@@ -2433,7 +2458,7 @@ function Canvas({
2433
2458
  onDelete: () => handleDelete(i, b.id)
2434
2459
  }
2435
2460
  ),
2436
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2461
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2437
2462
  BlockCard,
2438
2463
  {
2439
2464
  block: b,
@@ -2446,7 +2471,7 @@ function Canvas({
2446
2471
  )
2447
2472
  }
2448
2473
  ),
2449
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2474
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2450
2475
  BlockInserter,
2451
2476
  {
2452
2477
  index: i + 1,
@@ -2457,7 +2482,7 @@ function Canvas({
2457
2482
  ] }, b.id)) }) })
2458
2483
  }
2459
2484
  ),
2460
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2485
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2461
2486
  DocumentStarter,
2462
2487
  {
2463
2488
  autoFocus: list.blocks.length === 0,
@@ -2473,6 +2498,7 @@ function Canvas({
2473
2498
  function BlockCanvas({
2474
2499
  blocks,
2475
2500
  onChange,
2501
+ onDelete,
2476
2502
  blockTypes = coreBlocks,
2477
2503
  upload,
2478
2504
  resolveAssetUrl,
@@ -2486,11 +2512,12 @@ function BlockCanvas({
2486
2512
  () => ({ blockTypes, upload, resolveAssetUrl, onError }),
2487
2513
  [blockTypes, upload, resolveAssetUrl, onError]
2488
2514
  );
2489
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(QuoinProvider, { value: config, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2515
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(QuoinProvider, { value: config, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2490
2516
  Canvas,
2491
2517
  {
2492
2518
  blocks,
2493
2519
  onChange,
2520
+ onDelete,
2494
2521
  blockTypes,
2495
2522
  readOnly,
2496
2523
  className,
@@ -2502,6 +2529,9 @@ function BlockCanvas({
2502
2529
 
2503
2530
  // src/hooks/useAutosave.ts
2504
2531
  var React12 = __toESM(require("react"), 1);
2532
+ function reportedFailure(result) {
2533
+ return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
2534
+ }
2505
2535
  function useAutosave({
2506
2536
  deps,
2507
2537
  build,
@@ -2525,7 +2555,7 @@ function useAutosave({
2525
2555
  setStatus("saving");
2526
2556
  try {
2527
2557
  const res = await saveRef.current(buildRef.current());
2528
- if (res && res.ok === false) throw new Error("save rejected");
2558
+ if (reportedFailure(res)) throw new Error("save rejected");
2529
2559
  retryRef.current = 0;
2530
2560
  dirtyRef.current = false;
2531
2561
  setStatus("saved");
@@ -2541,10 +2571,10 @@ function useAutosave({
2541
2571
  });
2542
2572
  const seenRef = React12.useRef(null);
2543
2573
  React12.useEffect(() => {
2544
- const seen = seenRef.current;
2574
+ const seen2 = seenRef.current;
2545
2575
  seenRef.current = deps;
2546
- if (seen === null) return;
2547
- const unchanged = seen.length === deps.length && seen.every((v, i) => v === deps[i]);
2576
+ if (seen2 === null) return;
2577
+ const unchanged = seen2.length === deps.length && seen2.every((v, i) => v === deps[i]);
2548
2578
  if (!unchanged) dirtyRef.current = true;
2549
2579
  }, deps);
2550
2580
  React12.useEffect(() => {
@@ -2567,8 +2597,10 @@ function useAutosave({
2567
2597
  // Annotate the CommonJS export names for ESM import in node:
2568
2598
  0 && (module.exports = {
2569
2599
  BlockCanvas,
2600
+ FilePickerButton,
2570
2601
  QuoinProvider,
2571
2602
  TEXT_COMMANDS,
2603
+ UploadField,
2572
2604
  applyEdit,
2573
2605
  blockByType,
2574
2606
  caretRect,