@hwp-editor/react 1.3.0 → 1.4.1

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
@@ -58,7 +58,7 @@ module.exports = __toCommonJS(index_exports);
58
58
 
59
59
  // src/HwpEditor.tsx
60
60
  var import_react13 = require("react");
61
- var import_core19 = require("@hwp-editor/core");
61
+ var import_core20 = require("@hwp-editor/core");
62
62
 
63
63
  // src/context.ts
64
64
  var import_react = require("react");
@@ -75,7 +75,7 @@ function useHwpEditorContext() {
75
75
 
76
76
  // src/PageCanvas.tsx
77
77
  var import_react2 = require("react");
78
- var import_core2 = require("@hwp-editor/core");
78
+ var import_core3 = require("@hwp-editor/core");
79
79
 
80
80
  // src/geometry.ts
81
81
  var import_core = require("@hwp-editor/core");
@@ -130,21 +130,46 @@ function idIndex(envelope) {
130
130
  idIndexes.set(envelope, index);
131
131
  return index;
132
132
  }
133
+ var pathIndexes = /* @__PURE__ */ new WeakMap();
134
+ function pathIndex(envelope) {
135
+ const cached = pathIndexes.get(envelope);
136
+ if (cached !== void 0) return cached;
137
+ const index = /* @__PURE__ */ new Map();
138
+ for (const segment of envelope.segments) {
139
+ if (segment.id !== void 0 || segment.kind !== "para") continue;
140
+ const key = [segment.section, ...segment.indices ?? [segment.para]].join(".");
141
+ if (!index.has(key)) index.set(key, segment);
142
+ }
143
+ pathIndexes.set(envelope, index);
144
+ return index;
145
+ }
146
+ function rowSegment(envelope, rowId) {
147
+ const byId = idIndex(envelope).get(rowId);
148
+ if (byId !== void 0) return byId;
149
+ const path = (0, import_core.segmentIdPath)(rowId);
150
+ return path.length === 0 ? void 0 : pathIndex(envelope).get(path.join("."));
151
+ }
152
+ function hasIds(envelope) {
153
+ return envelope.segments.some((segment) => segment.id !== void 0);
154
+ }
155
+ function rowDraws(envelope, rowId, segment) {
156
+ if (segment.id !== void 0) return rowId === segment.id;
157
+ return rowSegment(envelope, rowId) === segment;
158
+ }
133
159
  function segmentOnPage(envelope, page, xFraction, yFraction, pageIndex, pageCount) {
134
- if (page.layout === void 0 || !envelope.segments.some((segment) => segment.id !== void 0)) {
160
+ if (page.layout === void 0 || !hasIds(envelope) && page.layout.rows.every((row) => rowSegment(envelope, row.id) === void 0)) {
135
161
  return nearestSegment(envelope, pageIndex, yFraction, pageCount);
136
162
  }
137
163
  const { width_pt, height_pt, rows } = page.layout;
138
164
  const x = clamp(xFraction, 0, 1) * width_pt;
139
165
  const y = clamp(yFraction, 0, 1) * height_pt;
140
- const byId = idIndex(envelope);
141
166
  let best;
142
167
  let bestVertical = Infinity;
143
168
  let bestHorizontal = Infinity;
144
169
  let bestArea = Infinity;
145
170
  for (const row of rows) {
146
171
  const box = row.box;
147
- const segment = byId.get(row.id);
172
+ const segment = rowSegment(envelope, row.id);
148
173
  if (box === null || segment === void 0) continue;
149
174
  const dx = Math.max(box.x0 - x, 0, x - box.x1);
150
175
  const dy = Math.max(box.y0 - y, 0, y - box.y1);
@@ -161,13 +186,13 @@ function segmentOnPage(envelope, page, xFraction, yFraction, pageIndex, pageCoun
161
186
  return best;
162
187
  }
163
188
  function bandsOnPage(envelope, segment, page, pageIndex, pageCount) {
164
- if (page.layout === void 0 || segment.id === void 0) {
189
+ if (page.layout === void 0) {
165
190
  const band = segmentBand(envelope, segment, pageCount);
166
- return band?.pageIndex === pageIndex ? [{ left: 0, top: band.top, width: 100, height: band.height }] : [];
191
+ return band?.pageIndex === pageIndex ? [{ left: 0, top: band.top, width: 100, height: band.height, approximate: true }] : [];
167
192
  }
168
193
  const target = ["run", "image", "field"].includes(segment.kind) && segment.indices !== void 0 ? envelope.segments.find((candidate) => candidate.kind === "para" && candidate.section === segment.section && candidate.indices !== void 0 && candidate.indices.length === segment.indices.length - 1 && candidate.indices.every((value, index) => value === segment.indices[index])) ?? segment : segment;
169
194
  const { width_pt, height_pt, rows } = page.layout;
170
- return rows.filter((row) => row.id === target.id && row.box !== null).map((row) => {
195
+ return rows.filter((row) => row.box !== null && rowDraws(envelope, row.id, target)).map((row) => {
171
196
  const box = row.box;
172
197
  return {
173
198
  left: box.x0 / width_pt * 100,
@@ -178,32 +203,8 @@ function bandsOnPage(envelope, segment, page, pageIndex, pageCount) {
178
203
  });
179
204
  }
180
205
 
181
- // src/sanitize.ts
182
- function sanitizeSvg(svgText) {
183
- const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
184
- const root = doc.documentElement;
185
- if (root === null || root.nodeName === "parsererror" || doc.getElementsByTagName("parsererror").length > 0) {
186
- return "";
187
- }
188
- const dangerous = root.querySelectorAll("script, foreignObject");
189
- for (const node of Array.from(dangerous)) node.remove();
190
- scrubElement(root);
191
- return root.outerHTML;
192
- }
193
- function scrubElement(el) {
194
- for (const attr of Array.from(el.attributes)) {
195
- const name = attr.name.toLowerCase();
196
- const value = attr.value.trim().toLowerCase();
197
- if (name.startsWith("on")) {
198
- el.removeAttribute(attr.name);
199
- } else if ((name === "href" || name === "xlink:href") && value.startsWith("javascript:")) {
200
- el.removeAttribute(attr.name);
201
- }
202
- }
203
- for (const child of Array.from(el.children)) scrubElement(child);
204
- }
205
-
206
206
  // src/text.ts
207
+ var import_core2 = require("@hwp-editor/core");
207
208
  function plainSegmentText(markdownSlice) {
208
209
  return markdownSlice.split("\n").map(stripLine).join("\n").trim();
209
210
  }
@@ -217,6 +218,41 @@ function stripLine(line) {
217
218
  out = out.replace(/`([^`]*)`/g, "$1");
218
219
  return out;
219
220
  }
221
+ var LIST_MARKER = /^[ \t]*(?:\d{1,9}[.)]|[-*+])[ \t]+/;
222
+ function paragraphSourceText(envelope, para, pages = []) {
223
+ const plain = plainSegmentText((0, import_core2.segmentText)(envelope, para));
224
+ const marker = LIST_MARKER.exec(plain);
225
+ if (marker === null) return plain;
226
+ const stripped = plain.slice(marker[0].length);
227
+ if (para.indices !== void 0) {
228
+ const first = envelope.segments.find((candidate) => candidate.kind === "run" && candidate.section === para.section && candidate.indices !== void 0 && candidate.indices.length === para.indices.length + 1 && para.indices.every((value, index) => candidate.indices[index] === value));
229
+ if (first !== void 0) {
230
+ const lead = [...(0, import_core2.segmentText)(envelope, para)].slice(0, Math.max(0, first.start - para.start)).join("");
231
+ return LIST_MARKER.test(lead) && lead.trim() === marker[0].trim() ? stripped : plain;
232
+ }
233
+ }
234
+ const length = sourceLength(para, pages);
235
+ if (length === void 0) return plain;
236
+ return stripped.length === length && plain.length !== length ? stripped : plain;
237
+ }
238
+ function sourceLength(para, pages) {
239
+ let start = Infinity;
240
+ let end = -Infinity;
241
+ for (const page of pages) {
242
+ for (const row of page.layout?.rows ?? []) {
243
+ if (row.kind !== "para" || row.source_chars === null || !rowMatches(row.id, para)) continue;
244
+ start = Math.min(start, row.source_chars.start);
245
+ end = Math.max(end, row.source_chars.end);
246
+ }
247
+ }
248
+ return end >= start ? end - start : void 0;
249
+ }
250
+ function rowMatches(rowId, segment) {
251
+ if (segment.id !== void 0) return rowId === segment.id;
252
+ const path = (0, import_core2.segmentIdPath)(rowId);
253
+ const own = [segment.section, ...segment.indices ?? [segment.para]];
254
+ return path.length === own.length && path.every((value, index) => value === own[index]);
255
+ }
220
256
 
221
257
  // src/PageCanvas.tsx
222
258
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -231,11 +267,11 @@ function PageCanvas() {
231
267
  if (envelope !== null) {
232
268
  const refs = state.selections?.length ? state.selections : selection === null ? [] : [selection];
233
269
  for (const ref of refs) {
234
- const segment = (0, import_core2.segmentAtRef)(envelope, ref);
270
+ const segment = (0, import_core3.segmentAtRef)(envelope, ref);
235
271
  if (segment !== void 0) selectedSegments.add(segment);
236
272
  }
237
273
  }
238
- if (pages.length === 0) {
274
+ if (pages.length === 0 || envelope === null) {
239
275
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hwped-canvas", role: "main", "aria-label": t("canvas.aria"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hwped-empty", children: t("canvas.emptyPages") }) });
240
276
  }
241
277
  const selectAt = (pageIndex, xFraction, yFraction, modifiers) => {
@@ -243,7 +279,7 @@ function PageCanvas() {
243
279
  const segment = segmentOnPage(envelope, pages[pageIndex], xFraction, yFraction, pageIndex, pages.length);
244
280
  if (segment !== void 0) {
245
281
  const mode = modifiers?.shiftKey ? "extend" : modifiers?.metaKey || modifiers?.ctrlKey ? "toggle" : "single";
246
- store.dispatch((0, import_core2.selectionAction)(envelope, store.getState(), segment, mode));
282
+ store.dispatch((0, import_core3.selectionAction)(envelope, store.getState(), segment, mode));
247
283
  }
248
284
  };
249
285
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hwped-canvas", role: "main", "aria-label": t("canvas.aria"), children: pages.map((page, pageIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -289,7 +325,7 @@ function bandsForPage(envelope, page, pageIndex, pageCount, selected, pendingKey
289
325
  bands.push({
290
326
  ...band,
291
327
  key: `${isSelected ? "sel" : "pend"}-${key}-${bandIdentity(segment)}-${index}`,
292
- className: isSelected ? "hwped-band hwped-band-selected" : "hwped-band hwped-band-pending"
328
+ className: (isSelected ? "hwped-band hwped-band-selected" : "hwped-band hwped-band-pending") + (band.approximate ? " hwped-band-approximate" : "")
293
329
  });
294
330
  }
295
331
  }
@@ -299,14 +335,9 @@ function PageView(props) {
299
335
  const { page, onClick, onActivate, bands } = props;
300
336
  const { t } = useHwpEditorContext();
301
337
  const label = t("page.label", { page: page.page });
302
- const svgHtml = (0, import_react2.useMemo)(
303
- () => page.format === "svg" ? sanitizeSvg(new TextDecoder().decode(page.data)) : null,
304
- [page]
305
- );
306
338
  const imgSrc = (0, import_react2.useMemo)(() => {
307
- if (page.format === "svg") return null;
308
- const mime = page.format === "jpeg" ? "image/jpeg" : `image/${page.format}`;
309
- return `data:${mime};base64,${import_core2.base64.encode(page.data)}`;
339
+ const mime = page.format === "jpeg" ? "image/jpeg" : page.format === "svg" ? "image/svg+xml" : `image/${page.format}`;
340
+ return `data:${mime};base64,${import_core3.base64.encode(page.data)}`;
310
341
  }, [page]);
311
342
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
312
343
  "div",
@@ -324,17 +355,11 @@ function PageView(props) {
324
355
  }
325
356
  },
326
357
  children: [
327
- svgHtml !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
328
- "div",
329
- {
330
- className: "hwped-page-svg",
331
- dangerouslySetInnerHTML: { __html: svgHtml }
332
- }
333
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
334
359
  "img",
335
360
  {
336
361
  className: "hwped-page-img",
337
- src: imgSrc ?? "",
362
+ src: imgSrc,
338
363
  alt: label,
339
364
  width: page.width,
340
365
  height: page.height
@@ -344,6 +369,7 @@ function PageView(props) {
344
369
  "div",
345
370
  {
346
371
  className: band.className,
372
+ "data-approximate": band.className.includes("hwped-band-approximate") ? "true" : void 0,
347
373
  style: { top: `${band.top}%`, height: `${band.height}%`, left: `${band.left}%`, width: `${band.width}%` }
348
374
  },
349
375
  band.key
@@ -376,7 +402,7 @@ function targetsSegment(op, envelope, segment) {
376
402
  return "run" in at ? segment.kind === "run" && segment.indices?.length === 2 && segment.indices[1] === at.run : segment.kind === "para" && (segment.indices === void 0 || segment.indices.length === 1);
377
403
  }
378
404
  const target = opSearchText(op);
379
- return target !== null && target !== "" && plainSegmentText((0, import_core2.segmentText)(envelope, segment)).includes(target);
405
+ return target !== null && target !== "" && plainSegmentText((0, import_core3.segmentText)(envelope, segment)).includes(target);
380
406
  }
381
407
 
382
408
  // src/unit-input.ts
@@ -387,7 +413,7 @@ function isPlainDecimal(value) {
387
413
 
388
414
  // src/SegmentInspector.tsx
389
415
  var import_react3 = require("react");
390
- var import_core3 = require("@hwp-editor/core");
416
+ var import_core4 = require("@hwp-editor/core");
391
417
  var import_jsx_runtime2 = require("react/jsx-runtime");
392
418
  var ALIGNMENTS = [
393
419
  { value: "left", key: "segment.alignLeft" },
@@ -403,21 +429,29 @@ var SHAPE_KEYS = [
403
429
  { value: "top", key: "segment.shape.top" },
404
430
  { value: "bottom", key: "segment.shape.bottom" }
405
431
  ];
432
+ var CLICK_REFUSAL_MESSAGE = {
433
+ cell: "segment.clickRefusedCell",
434
+ table: "segment.clickRefusedTable",
435
+ container: "segment.clickRefusedContainer",
436
+ unaddressable: "segment.clickRefusedUnaddressable",
437
+ unaddressableRuns: "segment.clickRefusedUnaddressableRuns",
438
+ noRuns: "segment.clickRefusedNoRuns"
439
+ };
406
440
  function SegmentInspector() {
407
441
  const { state, store, envelope, editable, capabilities, styles, stylesStatus, t, insertedHidden } = useHwpEditorContext();
408
442
  const { selection } = state;
409
443
  const paragraphKeys = /* @__PURE__ */ new Set();
410
444
  const paragraphs = (envelope?.segments.filter((candidate) => {
411
445
  if (candidate.kind !== "para") return false;
412
- if (!(0, import_core3.hasOwnRef)(candidate)) return false;
446
+ if (!(0, import_core4.hasOwnRef)(candidate)) return false;
413
447
  const key = candidate.id ?? `${candidate.section}:${candidate.para}`;
414
448
  if (paragraphKeys.has(key)) return false;
415
449
  paragraphKeys.add(key);
416
450
  return true;
417
- }) ?? []).sort(import_core3.compareSegments);
418
- const selected = envelope === null ? [] : (0, import_core3.selectedParagraphs)(envelope, state);
419
- const segment = envelope !== null && selection !== null ? (0, import_core3.segmentAtRef)(envelope, selection) : void 0;
420
- const targets = envelope !== null && segment !== void 0 ? (0, import_core3.resolveSelection)(envelope, segment) : void 0;
451
+ }) ?? []).sort(import_core4.compareSegments);
452
+ const selected = envelope === null ? [] : (0, import_core4.selectedParagraphs)(envelope, state);
453
+ const segment = envelope !== null && selection !== null ? (0, import_core4.segmentAtRef)(envelope, selection) : void 0;
454
+ const targets = envelope !== null && segment !== void 0 ? (0, import_core4.resolveSelection)(envelope, segment) : void 0;
421
455
  const paragraphTargets = targets?.paragraphs ?? [];
422
456
  const singleParagraph = targets?.paragraph;
423
457
  const directStyleRow = (() => {
@@ -425,13 +459,13 @@ function SegmentInspector() {
425
459
  if (id === void 0 || styles === null) return void 0;
426
460
  return styles.styles.find((item) => item.kind === "paragraph" && item.used_by.includes(id));
427
461
  })();
428
- const directStyleWhy = directStyleRow !== void 0 ? null : stylesStatus === "loading" ? "segment.directStyleLoading" : stylesStatus === "failed" ? "segment.directStyleFailed" : stylesStatus === "unsupported" ? "styles.unavailableHost" : capabilities === null ? "styles.checkingHost" : capabilities.styles !== "v1" ? (0, import_core3.stylesVersionSupported)(capabilities.version) ? "styles.unavailableHost" : "segment.directStyleUnavailable" : "segment.directStyleUnknown";
462
+ const directStyleWhy = directStyleRow !== void 0 ? null : stylesStatus === "loading" ? "segment.directStyleLoading" : stylesStatus === "failed" ? "segment.directStyleFailed" : stylesStatus === "unsupported" ? "styles.unavailableHost" : capabilities === null ? "styles.checkingHost" : capabilities.styles !== "v1" ? (0, import_core4.stylesVersionSupported)(capabilities.version) ? "styles.unavailableHost" : "segment.directStyleUnavailable" : "segment.directStyleUnknown";
429
463
  const refusalText = (reason) => reason === null || reason === void 0 ? null : reason === "cell" ? t("segment.cellSelectionRefused") : reason === "table" ? t("segment.tableSelectionRefused") : reason === "container" ? t("segment.containerSelectionRefused") : t("segment.noParagraphSelection");
430
- const clickRefusedText = (reason) => reason === null || reason === void 0 ? null : reason === "cell" ? t("segment.clickRefusedCell") : reason === "table" ? t("segment.clickRefusedTable") : reason === "container" ? t("segment.clickRefusedContainer") : t("segment.clickRefusedUnaddressable");
464
+ const clickRefusedText = (reason) => reason === null || reason === void 0 ? null : t(CLICK_REFUSAL_MESSAGE[reason]);
431
465
  const refusalReason2 = refusalText(targets?.refusal);
432
- const targetLabel = segment === void 0 ? "" : segment.kind === "cell" ? t("segment.selectedCell", { path: (0, import_core3.segmentPath)(segment), count: paragraphTargets.length }) : singleParagraph === void 0 ? "" : t("segment.selectedPara", { path: (0, import_core3.segmentPath)(singleParagraph) });
433
- const currentText = envelope !== null && paragraphTargets.length > 0 ? plainSegmentText(paragraphTargets.map((candidate) => (0, import_core3.segmentText)(envelope, candidate)).join("\n")) : "";
434
- const markdownText = envelope !== null && singleParagraph !== void 0 ? (0, import_core3.segmentText)(envelope, singleParagraph) : "";
466
+ const targetLabel = segment === void 0 ? "" : segment.kind === "cell" ? t("segment.selectedCell", { path: (0, import_core4.segmentPath)(segment), count: paragraphTargets.length }) : singleParagraph === void 0 ? "" : t("segment.selectedPara", { path: (0, import_core4.segmentPath)(singleParagraph) });
467
+ const currentText = envelope !== null && paragraphTargets.length > 0 ? paragraphTargets.map((candidate) => paragraphSourceText(envelope, candidate, state.pages)).join("\n").trim() : "";
468
+ const markdownText = envelope !== null && singleParagraph !== void 0 ? (0, import_core4.segmentText)(envelope, singleParagraph) : "";
435
469
  const supplementaryStarts = (0, import_react3.useMemo)(() => {
436
470
  const positions = [];
437
471
  let offset = 0;
@@ -467,19 +501,19 @@ function SegmentInspector() {
467
501
  setPlaceholderName("");
468
502
  }, [selection]);
469
503
  const find = currentText;
470
- const address = singleParagraph === void 0 ? void 0 : (0, import_core3.paragraphAddress)(singleParagraph);
504
+ const address = singleParagraph === void 0 ? void 0 : (0, import_core4.paragraphAddress)(singleParagraph);
471
505
  const siblings = singleParagraph?.indices === void 0 ? [] : paragraphs.filter((candidate) => candidate.section === singleParagraph.section && candidate.id !== void 0 && candidate.indices !== void 0 && candidate.indices.length === singleParagraph.indices.length && singleParagraph.indices.slice(0, -1).every((index, at) => candidate.indices[at] === index));
472
506
  const siblingAt = siblings.findIndex((candidate) => candidate.id === singleParagraph?.id);
473
507
  const addressedUnavailable = capabilities?.editOps !== "v1";
474
508
  const disabled = !editable || addressedUnavailable || state.status === "applying";
475
509
  const selectedRefs = state.selections?.length ? state.selections : selection === null ? [] : [selection];
476
510
  const explicitSegments = envelope === null ? [] : selectedRefs.flatMap((ref) => {
477
- const item = (0, import_core3.segmentAtRef)(envelope, ref);
511
+ const item = (0, import_core4.segmentAtRef)(envelope, ref);
478
512
  return item === void 0 ? [] : [item];
479
513
  });
480
514
  const hasRunSelection = explicitSegments.some((item) => item.kind === "run");
481
515
  const multiSelection = explicitSegments.length > 1;
482
- const paragraphRun = multiSelection && explicitSegments.every((item) => item.kind === "para") ? [...explicitSegments].sort(import_core3.compareSegments) : null;
516
+ const paragraphRun = multiSelection && explicitSegments.every((item) => item.kind === "para") ? [...explicitSegments].sort(import_core4.compareSegments) : null;
483
517
  const runStart = paragraphRun === null ? -1 : siblings.findIndex((candidate) => candidate.id === paragraphRun[0]?.id);
484
518
  const runContiguous = paragraphRun !== null && runStart >= 0 && paragraphRun.every((item, at) => siblings[runStart + at]?.id === item.id);
485
519
  const runEnd = runContiguous ? runStart + paragraphRun.length - 1 : -1;
@@ -490,7 +524,7 @@ function SegmentInspector() {
490
524
  if (insertionRun === null || !insertionCurrent || runParent === void 0) return [];
491
525
  const section = insertionRun[0].section;
492
526
  return insertedHidden.ids.flatMap((id) => {
493
- const [idSection, ...indices] = (0, import_core3.segmentIdPath)(id);
527
+ const [idSection, ...indices] = (0, import_core4.segmentIdPath)(id);
494
528
  if (idSection !== section || indices.length !== runParent.length + 1) return [];
495
529
  if (!runParent.every((index, at) => indices[at] === index)) return [];
496
530
  return [{ id, index: indices[indices.length - 1] }];
@@ -501,16 +535,16 @@ function SegmentInspector() {
501
535
  if (published === void 0) return void 0;
502
536
  const publishedIndex = published.indices?.[published.indices.length - 1];
503
537
  const beyond = hiddenInRun.filter((hidden) => publishedIndex === void 0 || (edge === "first" ? hidden.index < publishedIndex : hidden.index > publishedIndex));
504
- if (beyond.length === 0) return (0, import_core3.paragraphAddress)(published);
538
+ if (beyond.length === 0) return (0, import_core4.paragraphAddress)(published);
505
539
  const pick = beyond.reduce((best, hidden) => (edge === "first" ? hidden.index < best.index : hidden.index > best.index) ? hidden : best);
506
540
  return { id: pick.id };
507
541
  };
508
542
  const textSelectionRestricted = multiSelection || hasRunSelection || state.range != null;
509
543
  const singleTargetDisabled = disabled || textSelectionRestricted || address === void 0;
510
544
  const reorderRefused = multiSelection && !runContiguous;
511
- const definitionLocked = paragraphRun === null ? singleParagraph !== void 0 && (0, import_core3.isSectionDefinitionParagraph)(singleParagraph) : paragraphRun.some(import_core3.isSectionDefinitionParagraph);
545
+ const definitionLocked = paragraphRun === null ? singleParagraph !== void 0 && (0, import_core4.isSectionDefinitionParagraph)(singleParagraph) : paragraphRun.some(import_core4.isSectionDefinitionParagraph);
512
546
  const moveUpNeighbour = siblings[(paragraphRun === null ? siblingAt : runStart) - 1];
513
- const definitionAbove = moveUpNeighbour !== void 0 && (0, import_core3.isSectionDefinitionParagraph)(moveUpNeighbour);
547
+ const definitionAbove = moveUpNeighbour !== void 0 && (0, import_core4.isSectionDefinitionParagraph)(moveUpNeighbour);
514
548
  const deleteDisabled = disabled || definitionLocked || (paragraphRun === null ? textSelectionRestricted || address === void 0 : !runContiguous);
515
549
  const moveUpDisabled = deleteDisabled || definitionAbove || (paragraphRun === null ? siblingAt <= 0 : runStart <= 0);
516
550
  const moveDownDisabled = deleteDisabled || (paragraphRun === null ? siblingAt < 0 || siblingAt >= siblings.length - 1 : runEnd >= siblings.length - 1);
@@ -518,12 +552,12 @@ function SegmentInspector() {
518
552
  const legacyLinkSeparator = capabilities?.editOps !== "v1" && [currentText, linkText, linkUrl].some((value) => value.includes("=>"));
519
553
  const validShape = isPlainDecimal(shapeValue) && (shapeKey !== "line-spacing" || Number(shapeValue) > 0);
520
554
  const validFontSize = fontSize.trim() === "" || isPlainDecimal(fontSize) && Number(fontSize) > 0;
521
- const formatInputs = envelope === null ? [] : explicitSegments.flatMap((item) => item.kind === "run" ? [item] : (0, import_core3.resolveSelection)(envelope, item).paragraphs).sort(import_core3.compareSegments);
522
- const formatTargets = envelope === null ? [] : (0, import_core3.formatTargets)(envelope, formatInputs, state.range, (0, import_core3.documentFormat)(state.document?.name, state.document?.data));
523
- const runs = envelope === null || singleParagraph === void 0 ? [] : (0, import_core3.paragraphRuns)(envelope, singleParagraph);
555
+ const formatInputs = envelope === null ? [] : explicitSegments.flatMap((item) => item.kind === "run" ? [item] : (0, import_core4.resolveSelection)(envelope, item).paragraphs).sort(import_core4.compareSegments);
556
+ const formatTargets = envelope === null ? [] : (0, import_core4.formatTargets)(envelope, formatInputs, state.range, (0, import_core4.documentFormat)(state.document?.name, state.document?.data));
557
+ const runs = envelope === null || singleParagraph === void 0 ? [] : (0, import_core4.paragraphRuns)(envelope, singleParagraph);
524
558
  const formatNoRuns = state.range == null && singleParagraph !== void 0 && singleParagraph.start !== singleParagraph.end && singleParagraph.indices === void 0 && runs.length === 0;
525
559
  const rangeAvailable = !multiSelection && runs.some((run) => run.id !== void 0 || singleParagraph?.indices?.length === 1 && run.indices?.length === 2);
526
- const charPrimary = segment?.kind === "run" ? segment : envelope !== null && state.range != null && formatTargets[0]?.address.id !== void 0 ? (0, import_core3.segmentAtRef)(envelope, { id: formatTargets[0].address.id, section: 0, para: 0 }) ?? singleParagraph : singleParagraph;
560
+ const charPrimary = segment?.kind === "run" ? segment : envelope !== null && state.range != null && formatTargets[0]?.address.id !== void 0 ? (0, import_core4.segmentAtRef)(envelope, { id: formatTargets[0].address.id, section: 0, para: 0 }) ?? singleParagraph : singleParagraph;
527
561
  const plainParagraph = (() => {
528
562
  if (envelope === null || singleParagraph === void 0) return void 0;
529
563
  const depth = singleParagraph.indices?.length;
@@ -534,7 +568,7 @@ function SegmentInspector() {
534
568
  const contentEnd = singleParagraph.start + points.length;
535
569
  const holdsControl = envelope.segments.some((item) => (item.kind === "field" || item.kind === "image" || item.kind === "bookmark") && item.start < contentEnd && item.end > singleParagraph.start);
536
570
  if (holdsControl) return void 0;
537
- const ordered = (0, import_core3.paragraphRuns)(envelope, singleParagraph);
571
+ const ordered = (0, import_core4.paragraphRuns)(envelope, singleParagraph);
538
572
  let cursor = singleParagraph.start;
539
573
  for (const run of ordered) {
540
574
  if (run.start !== cursor || run.end < run.start || run.end > contentEnd) return void 0;
@@ -550,7 +584,7 @@ function SegmentInspector() {
550
584
  if (/^(?:[-+]|\d{1,3}[.)])\s/u.test(text)) return void 0;
551
585
  return { runs: ordered, texts, text };
552
586
  })();
553
- const rangeRuns = envelope !== null && singleParagraph !== void 0 && state.range != null ? (0, import_core3.paragraphRuns)(envelope, singleParagraph, state.range) : [];
587
+ const rangeRuns = envelope !== null && singleParagraph !== void 0 && state.range != null ? (0, import_core4.paragraphRuns)(envelope, singleParagraph, state.range) : [];
554
588
  const rangeFrom = rangeRuns.length === 0 ? -1 : Math.min(...rangeRuns.map((item) => item.start));
555
589
  const rangeTo = rangeRuns.length === 0 ? -1 : Math.max(...rangeRuns.map((item) => item.end));
556
590
  const wholeRuns = state.range != null && rangeRuns.length > 0 && rangeFrom === state.range.start && rangeTo === state.range.end;
@@ -583,7 +617,7 @@ function SegmentInspector() {
583
617
  };
584
618
  const activateSelection = (target, mode) => {
585
619
  if (envelope === null) return;
586
- store.dispatch((0, import_core3.selectionAction)(envelope, store.getState(), target, mode));
620
+ store.dispatch((0, import_core4.selectionAction)(envelope, store.getState(), target, mode));
587
621
  };
588
622
  const handleOptionKeyDown = (event, target) => {
589
623
  if (event.key !== "Enter" && event.key !== " ") return;
@@ -594,11 +628,11 @@ function SegmentInspector() {
594
628
  if (envelope === null || singleParagraph === void 0) return;
595
629
  const match = level === "char" ? charPrimary : singleParagraph;
596
630
  if (match === void 0) return;
597
- const refs = (0, import_core3.selectSimilar)(envelope, match, level);
631
+ const refs = (0, import_core4.selectSimilar)(envelope, match, level);
598
632
  if (refs.length === 0) return;
599
633
  const activeMatch = refs.find((ref) => {
600
- const item = (0, import_core3.segmentAtRef)(envelope, ref);
601
- const parent = item === void 0 ? void 0 : (0, import_core3.resolveSelection)(envelope, item).paragraph;
634
+ const item = (0, import_core4.segmentAtRef)(envelope, ref);
635
+ const parent = item === void 0 ? void 0 : (0, import_core4.resolveSelection)(envelope, item).paragraph;
602
636
  return parent === singleParagraph || parent?.id !== void 0 && parent.id === singleParagraph.id || parent !== void 0 && parent.id === void 0 && singleParagraph.id === void 0 && parent.section === singleParagraph.section && parent.para === singleParagraph.para;
603
637
  });
604
638
  const primary = activeMatch ?? refs[0];
@@ -606,7 +640,10 @@ function SegmentInspector() {
606
640
  type: "selectMany",
607
641
  selections: refs,
608
642
  primary,
609
- ...state.selectionAnchor === null || state.selectionAnchor === void 0 ? { anchor: primary } : { anchor: state.selectionAnchor }
643
+ // A matching-character-format selection is a new run selection, so its
644
+ // anchor is its own primary (#71 rule 7), never a run left over from an
645
+ // earlier one. The paragraph level keeps an existing anchor as before.
646
+ ...level === "char" || state.selectionAnchor === null || state.selectionAnchor === void 0 ? { anchor: primary } : { anchor: state.selectionAnchor }
610
647
  });
611
648
  };
612
649
  const moveParagraph = (direction) => {
@@ -618,7 +655,7 @@ function SegmentInspector() {
618
655
  type: "queueOp",
619
656
  op: {
620
657
  kind: "move-para",
621
- address: (0, import_core3.paragraphAddress)(neighbour2),
658
+ address: (0, import_core4.paragraphAddress)(neighbour2),
622
659
  to: { address: anchor2, position: direction === -1 ? "after" : "before" }
623
660
  }
624
661
  });
@@ -632,7 +669,7 @@ function SegmentInspector() {
632
669
  op: {
633
670
  kind: "move-para",
634
671
  address,
635
- to: { address: (0, import_core3.paragraphAddress)(neighbour2), position: direction === -1 ? "before" : "after" }
672
+ to: { address: (0, import_core4.paragraphAddress)(neighbour2), position: direction === -1 ? "before" : "after" }
636
673
  }
637
674
  });
638
675
  return;
@@ -645,7 +682,7 @@ function SegmentInspector() {
645
682
  type: "queueOp",
646
683
  op: {
647
684
  kind: "move-para",
648
- address: (0, import_core3.paragraphAddress)(neighbour),
685
+ address: (0, import_core4.paragraphAddress)(neighbour),
649
686
  to: { address: anchor, position: direction === -1 ? "after" : "before" }
650
687
  }
651
688
  });
@@ -670,7 +707,7 @@ function SegmentInspector() {
670
707
  store.dispatch({
671
708
  type: "queueOps",
672
709
  ops: [
673
- ...paragraphRun.map((item) => ({ kind: "delete-para", address: (0, import_core3.paragraphAddress)(item) })),
710
+ ...paragraphRun.map((item) => ({ kind: "delete-para", address: (0, import_core4.paragraphAddress)(item) })),
674
711
  // The insertion's unpublished paragraphs go with it (#521).
675
712
  ...hiddenInRun.map((hidden) => ({ kind: "delete-para", address: { id: hidden.id } }))
676
713
  ]
@@ -694,7 +731,7 @@ function SegmentInspector() {
694
731
  style: { maxHeight: "12rem", overflowY: "auto" },
695
732
  children: paragraphs.map((candidate, index) => {
696
733
  const isSelected = selected.some((item) => item === candidate || item.id !== void 0 && item.id === candidate.id || item.id === void 0 && candidate.id === void 0 && item.section === candidate.section && item.para === candidate.para);
697
- const snippet = plainSegmentText((0, import_core3.segmentText)(envelope, candidate)).trim().replace(/\s+/g, " ");
734
+ const snippet = plainSegmentText((0, import_core4.segmentText)(envelope, candidate)).trim().replace(/\s+/g, " ");
698
735
  const text = snippet.length > 96 ? `${snippet.slice(0, 93)}...` : snippet || t("segment.emptyPara");
699
736
  const label = t("segment.choiceLabel", { index: index + 1, text });
700
737
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -720,9 +757,9 @@ function SegmentInspector() {
720
757
  // state holds and the targets the format op will address.
721
758
  children: hasRunSelection ? t("segment.selectedRunCount", { count: explicitSegments.length }) : t("segment.selectedCount", { count: selected.length })
722
759
  }),
723
- // A run-level selection formats runs, and a modifier click from it
724
- // starts a paragraph selection over instead of joining the runs.
725
- hasRunSelection && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "hwped-hint", role: "status", children: t("segment.runSelectionTerminal") }),
760
+ // A run-level selection formats runs, and a modifier click keeps it at
761
+ // run level: Ctrl toggles a unit's runs, Shift extends by run (#71).
762
+ hasRunSelection && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "hwped-hint", role: "status", children: t("segment.runSelectionGestures") }),
726
763
  // A modifier click that had no paragraph to add left the selection
727
764
  // alone, and reports the click here whatever the selection's own
728
765
  // reason is: the reader's action is what was refused, and a click
@@ -870,7 +907,7 @@ function SegmentInspector() {
870
907
  disabled: disabled || selected.length === 0,
871
908
  onClick: () => store.dispatch({
872
909
  type: "queueOps",
873
- ops: selected.map((target) => ({ kind: "set-align", address: (0, import_core3.paragraphAddress)(target), alignment: align.value }))
910
+ ops: selected.map((target) => ({ kind: "set-align", address: (0, import_core4.paragraphAddress)(target), alignment: align.value }))
874
911
  }),
875
912
  children: t(align.key)
876
913
  },
@@ -916,7 +953,7 @@ function SegmentInspector() {
916
953
  type: "button",
917
954
  className: "hwped-btn",
918
955
  disabled: disabled || selected.length === 0 || !validShape,
919
- onClick: () => store.dispatch({ type: "queueOps", ops: selected.map((target) => ({ kind: "set-para", address: (0, import_core3.paragraphAddress)(target), key: shapeKey, value: `${shapeValue.trim()}${shapeKey === "line-spacing" ? lineSpacingUnit : "mm"}` })) }),
956
+ onClick: () => store.dispatch({ type: "queueOps", ops: selected.map((target) => ({ kind: "set-para", address: (0, import_core4.paragraphAddress)(target), key: shapeKey, value: `${shapeValue.trim()}${shapeKey === "line-spacing" ? lineSpacingUnit : "mm"}` })) }),
920
957
  children: t("segment.setPara")
921
958
  }
922
959
  )
@@ -932,7 +969,7 @@ function SegmentInspector() {
932
969
  charPrimary?.directChar === void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "hwped-hint", children: t("segment.similarCharUnavailable") }),
933
970
  singleParagraph.directPara === void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "hwped-hint", children: t("segment.similarParaUnavailable") })
934
971
  ] }),
935
- singleParagraph !== void 0 && (0, import_core3.hasDirectFormatting)(singleParagraph) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hwped-field", children: [
972
+ singleParagraph !== void 0 && (0, import_core4.hasDirectFormatting)(singleParagraph) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hwped-field", children: [
936
973
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hwped-label", children: t("segment.directFormatting") }),
937
974
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "hwped-hint", children: t("segment.directFormattingHint") }),
938
975
  directStyleRow === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "hwped-hint", children: t(directStyleWhy ?? "segment.directStyleUnknown") }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -941,7 +978,7 @@ function SegmentInspector() {
941
978
  type: "button",
942
979
  className: "hwped-btn",
943
980
  disabled,
944
- onClick: () => store.dispatch({ type: "queueOp", op: { kind: "apply-style", address: (0, import_core3.paragraphAddress)(singleParagraph), style: directStyleRow.id, clearDirect: true } }),
981
+ onClick: () => store.dispatch({ type: "queueOp", op: { kind: "apply-style", address: (0, import_core4.paragraphAddress)(singleParagraph), style: directStyleRow.id, clearDirect: true } }),
945
982
  children: t("segment.clearDirect")
946
983
  }
947
984
  )
@@ -1015,7 +1052,7 @@ function SegmentInspector() {
1015
1052
  }
1016
1053
  setRangeSplit(false);
1017
1054
  store.dispatch({ type: "setRange", range: start === end ? null : {
1018
- paragraph: (0, import_core3.segmentRef)(singleParagraph),
1055
+ paragraph: (0, import_core4.segmentRef)(singleParagraph),
1019
1056
  start: singleParagraph.start + start,
1020
1057
  end: singleParagraph.start + end
1021
1058
  } });
@@ -1033,7 +1070,7 @@ function SegmentInspector() {
1033
1070
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "hwped-btn", onClick: () => {
1034
1071
  setRangeSplit(false);
1035
1072
  rangeInput.current?.setSelectionRange(0, 0);
1036
- if (hasRunSelection) store.dispatch({ type: "select", selection: (0, import_core3.segmentRef)(singleParagraph) });
1073
+ if (hasRunSelection) store.dispatch({ type: "select", selection: (0, import_core4.segmentRef)(singleParagraph) });
1037
1074
  else store.dispatch({ type: "setRange", range: null });
1038
1075
  }, children: t("segment.rangeClear") })
1039
1076
  ] })
@@ -1193,7 +1230,7 @@ function wholeRunText(run, markdown) {
1193
1230
 
1194
1231
  // src/StylesPanel.tsx
1195
1232
  var import_react4 = require("react");
1196
- var import_core4 = require("@hwp-editor/core");
1233
+ var import_core5 = require("@hwp-editor/core");
1197
1234
  var import_jsx_runtime3 = require("react/jsx-runtime");
1198
1235
  var ALIGNMENTS2 = ["justify", "left", "right", "center", "distribute"];
1199
1236
  var ALIGN_LABEL = {
@@ -1266,7 +1303,7 @@ function DefinitionForm(props) {
1266
1303
  type: "queueOps",
1267
1304
  ops: paragraphs.map((paragraph) => ({
1268
1305
  kind: "apply-style",
1269
- address: (0, import_core4.paragraphAddress)(paragraph),
1306
+ address: (0, import_core5.paragraphAddress)(paragraph),
1270
1307
  style: row.id
1271
1308
  }))
1272
1309
  });
@@ -1390,12 +1427,12 @@ function StylesPanel() {
1390
1427
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hwped-panel", role: "region", "aria-label": t("tabs.styles"), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "hwped-hint", role: "status", children: t("styles.checkingHost") }) });
1391
1428
  }
1392
1429
  if (capabilities.styles !== "v1") {
1393
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hwped-panel", role: "region", "aria-label": t("tabs.styles"), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "hwped-hint", role: "status", children: (0, import_core4.stylesVersionSupported)(capabilities.version) ? t("styles.unavailableHost") : t("styles.unavailable", { version: capabilities.version }) }) });
1430
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hwped-panel", role: "region", "aria-label": t("tabs.styles"), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "hwped-hint", role: "status", children: (0, import_core5.stylesVersionSupported)(capabilities.version) ? t("styles.unavailableHost") : t("styles.unavailable", { version: capabilities.version }) }) });
1394
1431
  }
1395
1432
  if (engine.styles === void 0) {
1396
1433
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hwped-panel", role: "region", "aria-label": t("tabs.styles"), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "hwped-hint", role: "status", children: t("styles.unavailableHost") }) });
1397
1434
  }
1398
- const paragraphs = envelope === null ? [] : (0, import_core4.selectedParagraphs)(envelope, state);
1435
+ const paragraphs = envelope === null ? [] : (0, import_core5.selectedParagraphs)(envelope, state);
1399
1436
  const rows = styles?.styles ?? [];
1400
1437
  const row = rows.find((item) => item.id === selectedId) ?? null;
1401
1438
  const paragraphRow = row !== null && row.kind === "paragraph" ? row : null;
@@ -1405,8 +1442,8 @@ function StylesPanel() {
1405
1442
  setSelectedId(item.id);
1406
1443
  if (envelope === null || item.used_by.length === 0) return;
1407
1444
  const refs = item.used_by.flatMap((id) => {
1408
- const segment = (0, import_core4.segmentAtRef)(envelope, { id, section: 0, para: 0 });
1409
- return segment === void 0 ? [] : [(0, import_core4.segmentRef)(segment)];
1445
+ const segment = (0, import_core5.segmentAtRef)(envelope, { id, section: 0, para: 0 });
1446
+ return segment === void 0 ? [] : [(0, import_core5.segmentRef)(segment)];
1410
1447
  });
1411
1448
  if (refs.length === 0) return;
1412
1449
  store.dispatch({ type: "selectMany", selections: refs, primary: refs[0], anchor: refs[0] });
@@ -1417,12 +1454,12 @@ function StylesPanel() {
1417
1454
  const shape = base === void 0 ? void 0 : base.direct ?? base.style;
1418
1455
  if (name === "" || base === void 0 || shape === void 0 || envelope === null) return;
1419
1456
  const face = firstFace(shape);
1420
- const paragraph = (0, import_core4.resolveSelection)(envelope, base).paragraph ?? base;
1457
+ const paragraph = (0, import_core5.resolveSelection)(envelope, base).paragraph ?? base;
1421
1458
  const id = paragraph.id;
1422
1459
  const own = id === void 0 ? void 0 : rows.find((item) => item.kind === "paragraph" && item.used_by.includes(id));
1423
- const targets = (0, import_core4.selectSimilar)(envelope, paragraph, "para").flatMap((ref) => {
1424
- const segment = (0, import_core4.segmentAtRef)(envelope, ref);
1425
- return segment === void 0 ? [] : [(0, import_core4.paragraphAddress)(segment)];
1460
+ const targets = (0, import_core5.selectSimilar)(envelope, paragraph, "para").flatMap((ref) => {
1461
+ const segment = (0, import_core5.segmentAtRef)(envelope, ref);
1462
+ return segment === void 0 ? [] : [(0, import_core5.paragraphAddress)(segment)];
1426
1463
  });
1427
1464
  const align = shape.para === void 0 ? void 0 : writableAlignment(shape.para.alignment);
1428
1465
  store.dispatch({
@@ -1458,7 +1495,7 @@ function StylesPanel() {
1458
1495
  const switchPreset = () => {
1459
1496
  if (styles === null || envelope === null) return;
1460
1497
  const epoch = loadEpoch();
1461
- const styleOps = (0, import_core4.presetStyleOps)(styles, preset, envelope);
1498
+ const styleOps = (0, import_core5.presetStyleOps)(styles, preset, envelope);
1462
1499
  const carriedStyleOp = styleOps.length > 0;
1463
1500
  const batch = [
1464
1501
  ...styleOps,
@@ -1468,10 +1505,10 @@ function StylesPanel() {
1468
1505
  let unstyled = false;
1469
1506
  let changed = false;
1470
1507
  void replaceDocument(async (current) => {
1471
- const styled = await engine.edit(current, batch, (0, import_core4.presetBatchOptions)(batch, { verify: true }));
1472
- const reading = (0, import_core4.readPresetBatch)(styled.report);
1508
+ const styled = await engine.edit(current, batch, (0, import_core5.presetBatchOptions)(batch, { verify: true }));
1509
+ const reading = (0, import_core5.readPresetBatch)(styled.report);
1473
1510
  if (reading.refusal !== null) {
1474
- throw new import_core4.HwpEngineError(reading.refusal.code, reading.refusal.message);
1511
+ throw new import_core5.HwpEngineError(reading.refusal.code, reading.refusal.message);
1475
1512
  }
1476
1513
  unstyled = reading.unstyledTable;
1477
1514
  changed = !reading.unchanged;
@@ -1554,7 +1591,7 @@ function StylesPanel() {
1554
1591
  disabled,
1555
1592
  "aria-label": t("styles.presetLabel"),
1556
1593
  onChange: (e) => setPreset(e.target.value),
1557
- children: import_core4.STYLE_TABLE_PRESETS.map((value) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value, children: t(PRESET_LABEL[value]) }, value))
1594
+ children: import_core5.STYLE_TABLE_PRESETS.map((value) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value, children: t(PRESET_LABEL[value]) }, value))
1558
1595
  }
1559
1596
  ),
1560
1597
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -1575,10 +1612,10 @@ function StylesPanel() {
1575
1612
 
1576
1613
  // src/TableGrid.tsx
1577
1614
  var import_react5 = require("react");
1578
- var import_core6 = require("@hwp-editor/core");
1615
+ var import_core7 = require("@hwp-editor/core");
1579
1616
 
1580
1617
  // src/tables.ts
1581
- var import_core5 = require("@hwp-editor/core");
1618
+ var import_core6 = require("@hwp-editor/core");
1582
1619
  var GFM_DELIMITER_CELL = /^:?-{2,}:?$/;
1583
1620
  function endsWithCellDelimiter(row) {
1584
1621
  if (!row.endsWith("|")) return false;
@@ -1645,7 +1682,7 @@ function findTables(envelope) {
1645
1682
  tables.push({
1646
1683
  tableIndex: tables.length,
1647
1684
  segment,
1648
- rows: parseMarkdownTable((0, import_core5.segmentText)(envelope, segment))
1685
+ rows: parseMarkdownTable((0, import_core6.segmentText)(envelope, segment))
1649
1686
  });
1650
1687
  }
1651
1688
  return tables;
@@ -1715,7 +1752,7 @@ function TableGrid() {
1715
1752
  }
1716
1753
  )
1717
1754
  ] });
1718
- const mismatch = envelope === null ? null : (0, import_core6.tableCountMismatch)(envelope);
1755
+ const mismatch = envelope === null ? null : (0, import_core7.tableCountMismatch)(envelope);
1719
1756
  if (mismatch !== null) {
1720
1757
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hwped-panel", role: "region", "aria-label": t("table.panelAria"), children: [
1721
1758
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "hwped-hint", children: t("table.indexMismatch", mismatch) }),
@@ -1950,10 +1987,10 @@ function TableGrid() {
1950
1987
 
1951
1988
  // src/FieldsPanel.tsx
1952
1989
  var import_react6 = require("react");
1953
- var import_core8 = require("@hwp-editor/core");
1990
+ var import_core9 = require("@hwp-editor/core");
1954
1991
 
1955
1992
  // src/fields.ts
1956
- var import_core7 = require("@hwp-editor/core");
1993
+ var import_core8 = require("@hwp-editor/core");
1957
1994
  var SLOT_PATTERN = /\{\{\s*([^{}]+?)\s*\}\}/g;
1958
1995
  var CONTROL_KINDS = /* @__PURE__ */ new Set(["field", "image", "bookmark"]);
1959
1996
  function innermostAt(envelope, offset) {
@@ -1981,10 +2018,10 @@ function extractFieldSlots(envelope) {
1981
2018
  const offset = [...envelope.markdown.slice(0, match.index ?? 0)].length;
1982
2019
  const hit = innermostAt(envelope, offset);
1983
2020
  if (hit === void 0 || seen.has(name)) continue;
1984
- const paragraph = (0, import_core7.resolveSelection)(envelope, hit).paragraph;
2021
+ const paragraph = (0, import_core8.resolveSelection)(envelope, hit).paragraph;
1985
2022
  if (paragraph === void 0) continue;
1986
2023
  seen.add(name);
1987
- slots.push({ name, segment: (0, import_core7.segmentRef)(paragraph) });
2024
+ slots.push({ name, segment: (0, import_core8.segmentRef)(paragraph) });
1988
2025
  }
1989
2026
  return slots;
1990
2027
  }
@@ -2018,8 +2055,8 @@ function FieldsPanel() {
2018
2055
  children: [
2019
2056
  legacy && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "hwped-hint", children: t("fields.legacySeparatorHint") }),
2020
2057
  slots.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "hwped-hint", children: t("fields.hint") }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "hwped-fields", children: slots.map((slot) => {
2021
- const segment = envelope === null ? void 0 : (0, import_core8.segmentAtRef)(envelope, slot.segment);
2022
- const snippet = envelope === null || segment === void 0 ? "" : (0, import_core8.segmentText)(envelope, segment).trim().slice(0, 40);
2058
+ const segment = envelope === null ? void 0 : (0, import_core9.segmentAtRef)(envelope, slot.segment);
2059
+ const snippet = envelope === null || segment === void 0 ? "" : (0, import_core9.segmentText)(envelope, segment).trim().slice(0, 40);
2023
2060
  const draft = drafts[slot.name] ?? "";
2024
2061
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "hwped-field-item", children: [
2025
2062
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hwped-row", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -2088,19 +2125,19 @@ function FieldsPanel() {
2088
2125
 
2089
2126
  // src/TemplatePanel.tsx
2090
2127
  var import_react7 = require("react");
2091
- var import_core12 = require("@hwp-editor/core");
2128
+ var import_core13 = require("@hwp-editor/core");
2092
2129
 
2093
2130
  // src/errors.ts
2094
- var import_core9 = require("@hwp-editor/core");
2131
+ var import_core10 = require("@hwp-editor/core");
2095
2132
  function toEditorError(e) {
2096
2133
  const refusal = engineRefusalKind(e);
2097
- const base = (0, import_core9.isHwpEngineError)(e) && (0, import_core9.isHwpErrorCode)(e.code) ? { code: e.code, message: e.message } : { message: e instanceof Error ? e.message : String(e) };
2134
+ const base = (0, import_core10.isHwpEngineError)(e) && (0, import_core10.isHwpErrorCode)(e.code) ? { code: e.code, message: e.message } : { message: e instanceof Error ? e.message : String(e) };
2098
2135
  return refusal === void 0 ? base : { ...base, refusal };
2099
2136
  }
2100
2137
  function engineRefusalKind(e) {
2101
2138
  if (typeof e !== "object" || e === null) return void 0;
2102
2139
  const refusal = e.refusal;
2103
- return (0, import_core9.isEditRefusalKind)(refusal) ? refusal : void 0;
2140
+ return (0, import_core10.isEditRefusalKind)(refusal) ? refusal : void 0;
2104
2141
  }
2105
2142
  function engineStoppedWithoutDiagnostic(e) {
2106
2143
  return typeof e === "object" && e !== null && e.stopped === true;
@@ -2170,8 +2207,8 @@ function ErrorLine(props) {
2170
2207
  }
2171
2208
 
2172
2209
  // src/template-support.ts
2173
- var import_core10 = require("@hwp-editor/core");
2174
2210
  var import_core11 = require("@hwp-editor/core");
2211
+ var import_core12 = require("@hwp-editor/core");
2175
2212
  var METHOD_LABEL_KEYS = {
2176
2213
  slots: "templates.methodSlots",
2177
2214
  fill: "templates.methodFill",
@@ -2181,7 +2218,7 @@ var METHOD_LABEL_KEYS = {
2181
2218
  create: "templates.methodCreate"
2182
2219
  };
2183
2220
  function templateMethodStatus(engine, capabilities, method) {
2184
- if (!(0, import_core10.templateVersionSupported)(capabilities?.version ?? "")) {
2221
+ if (!(0, import_core11.templateVersionSupported)(capabilities?.version ?? "")) {
2185
2222
  return { supported: false, reason: "version" };
2186
2223
  }
2187
2224
  if (typeof engine[method] !== "function") {
@@ -2193,7 +2230,7 @@ function templateMethodStatus(engine, capabilities, method) {
2193
2230
  return { supported: true, reason: null };
2194
2231
  }
2195
2232
  function templateStatusReason(t, status, method) {
2196
- if (status.reason === "version") return t("templates.versionRequired", { version: import_core11.TEMPLATE_MIN_VERSION });
2233
+ if (status.reason === "version") return t("templates.versionRequired", { version: import_core12.TEMPLATE_MIN_VERSION });
2197
2234
  const label = t(METHOD_LABEL_KEYS[method]);
2198
2235
  if (status.reason === "missing") return t("templates.methodMissing", { method: label });
2199
2236
  if (status.reason === "unadvertised") return t("templates.methodUnadvertised", { method: label });
@@ -2215,7 +2252,7 @@ function initialListRow(fields, supplied = {}) {
2215
2252
  }
2216
2253
  function pageData(page) {
2217
2254
  const mime = page.format === "svg" ? "image/svg+xml" : `image/${page.format}`;
2218
- return `data:${mime};base64,${import_core12.base64.encode(page.data)}`;
2255
+ return `data:${mime};base64,${import_core13.base64.encode(page.data)}`;
2219
2256
  }
2220
2257
  function kindLabel(input, t) {
2221
2258
  if (input.kind === "slot") return t("templates.kindSlot");
@@ -2706,7 +2743,7 @@ function DocumentPanel() {
2706
2743
 
2707
2744
  // src/ImagePanel.tsx
2708
2745
  var import_react10 = require("react");
2709
- var import_core13 = require("@hwp-editor/core");
2746
+ var import_core14 = require("@hwp-editor/core");
2710
2747
  var import_jsx_runtime10 = require("react/jsx-runtime");
2711
2748
  function ImagePanel() {
2712
2749
  const { state, store, envelope, capabilities, editable, queueImage, onError, t } = useHwpEditorContext();
@@ -2744,14 +2781,14 @@ function ImagePanel() {
2744
2781
  if (file === void 0) return;
2745
2782
  setReading(true);
2746
2783
  try {
2747
- if (file.size === 0 || file.size > import_core13.MAX_IMAGE_ASSET_BYTES) throw new Error(t("image.invalidSize"));
2784
+ if (file.size === 0 || file.size > import_core14.MAX_IMAGE_ASSET_BYTES) throw new Error(t("image.invalidSize"));
2748
2785
  let bytes;
2749
2786
  try {
2750
2787
  bytes = await file.arrayBuffer();
2751
2788
  } catch {
2752
2789
  throw new Error(t("image.readError"));
2753
2790
  }
2754
- const data = new Uint8Array(bytes), mediaType = (0, import_core13.imageAssetMediaType)(data);
2791
+ const data = new Uint8Array(bytes), mediaType = (0, import_core14.imageAssetMediaType)(data);
2755
2792
  if (mediaType === null) throw new Error(t("image.invalidType"));
2756
2793
  if (generation.current === version && store.getState().document === source) {
2757
2794
  setChosen({ name: file.name, data, extension: mediaType === "image/png" ? "png" : "jpg" });
@@ -2809,12 +2846,12 @@ function ImagePanel() {
2809
2846
 
2810
2847
  // src/ComposePanel.tsx
2811
2848
  var import_react11 = require("react");
2812
- var import_core16 = require("@hwp-editor/core");
2849
+ var import_core17 = require("@hwp-editor/core");
2813
2850
 
2814
2851
  // src/presets.ts
2815
- var import_core14 = require("@hwp-editor/core");
2816
- var COMPOSE_PRESETS = [...import_core14.DOCUMENT_PROFILE_NAMES];
2817
- var PRESET_PROFILES = import_core14.DOCUMENT_PROFILES;
2852
+ var import_core15 = require("@hwp-editor/core");
2853
+ var COMPOSE_PRESETS = [...import_core15.DOCUMENT_PROFILE_NAMES];
2854
+ var PRESET_PROFILES = import_core15.DOCUMENT_PROFILES;
2818
2855
  function bodyToBlocks(body) {
2819
2856
  const blocks = [];
2820
2857
  for (const chunk of body.split(/\n\s*\n/)) {
@@ -2875,17 +2912,17 @@ function buildDocumentSpec(preset, input) {
2875
2912
  },
2876
2913
  heading: {
2877
2914
  font_family: profile.fontFamily,
2878
- font_size_pt: profile.bodySizePt + import_core14.COMPOSE_STYLE_ROWS.heading.sizeDeltaPt,
2915
+ font_size_pt: profile.bodySizePt + import_core15.COMPOSE_STYLE_ROWS.heading.sizeDeltaPt,
2879
2916
  bold: true,
2880
- spacing_before_pt: import_core14.COMPOSE_STYLE_ROWS.heading.spacingBeforePt,
2881
- spacing_after_pt: import_core14.COMPOSE_STYLE_ROWS.heading.spacingAfterPt
2917
+ spacing_before_pt: import_core15.COMPOSE_STYLE_ROWS.heading.spacingBeforePt,
2918
+ spacing_after_pt: import_core15.COMPOSE_STYLE_ROWS.heading.spacingAfterPt
2882
2919
  },
2883
2920
  title: {
2884
2921
  font_family: profile.fontFamily,
2885
- font_size_pt: profile.bodySizePt + import_core14.COMPOSE_STYLE_ROWS.title.sizeDeltaPt,
2922
+ font_size_pt: profile.bodySizePt + import_core15.COMPOSE_STYLE_ROWS.title.sizeDeltaPt,
2886
2923
  bold: true,
2887
2924
  align: "center",
2888
- spacing_after_pt: import_core14.COMPOSE_STYLE_ROWS.title.spacingAfterPt
2925
+ spacing_after_pt: import_core15.COMPOSE_STYLE_ROWS.title.spacingAfterPt
2889
2926
  }
2890
2927
  },
2891
2928
  sections: [section]
@@ -2894,7 +2931,7 @@ function buildDocumentSpec(preset, input) {
2894
2931
  }
2895
2932
 
2896
2933
  // src/components-support.ts
2897
- var import_core15 = require("@hwp-editor/core");
2934
+ var import_core16 = require("@hwp-editor/core");
2898
2935
  function millimetres(value) {
2899
2936
  return `${Math.round(value * 10) / 10} mm`;
2900
2937
  }
@@ -2950,7 +2987,7 @@ function insertionPreflight(blocks, documentName, t) {
2950
2987
  function insertionOf(error) {
2951
2988
  if (typeof error !== "object" || error === null) return void 0;
2952
2989
  const value = error.insertion;
2953
- return (0, import_core15.isInsertionRefusal)(value) ? value : void 0;
2990
+ return (0, import_core16.isInsertionRefusal)(value) ? value : void 0;
2954
2991
  }
2955
2992
  function overflowingBodyMm(error) {
2956
2993
  const insertion = insertionOf(error);
@@ -2960,10 +2997,10 @@ function isLayoutOverflow(error) {
2960
2997
  return insertionOf(error)?.kind === "layout_overflow";
2961
2998
  }
2962
2999
  function wrapped(error, text, detail) {
2963
- if ((0, import_core15.isHwpEngineError)(error)) {
3000
+ if ((0, import_core16.isHwpEngineError)(error)) {
2964
3001
  const insertion = insertionOf(error);
2965
3002
  return Object.assign(
2966
- new import_core15.HwpEngineError(error.code, text, {
3003
+ new import_core16.HwpEngineError(error.code, text, {
2967
3004
  ...error.status === void 0 ? {} : { status: error.status },
2968
3005
  cause: error,
2969
3006
  ...insertion === void 0 ? {} : { insertion },
@@ -2984,7 +3021,7 @@ function refusalReason(message) {
2984
3021
  }
2985
3022
  function isRefusedInsertion(error) {
2986
3023
  if (insertionOf(error) !== void 0) return true;
2987
- return (0, import_core15.isHwpEngineError)(error) && (error.code === "failed" || error.code === "bad_request");
3024
+ return (0, import_core16.isHwpEngineError)(error) && (error.code === "failed" || error.code === "bad_request");
2988
3025
  }
2989
3026
  function insertionFailure(error, blocks, t) {
2990
3027
  const message = error instanceof Error ? error.message : String(error);
@@ -3012,7 +3049,7 @@ var STEP_KEYS = {
3012
3049
  component_load: "components.step.load"
3013
3050
  };
3014
3051
  function componentRefusalText(error, t) {
3015
- if (!(0, import_core15.isComponentError)(error)) return null;
3052
+ if (!(0, import_core16.isComponentError)(error)) return null;
3016
3053
  return t("components.refused", {
3017
3054
  code: error.code,
3018
3055
  source: error.source,
@@ -3041,7 +3078,7 @@ var PRESET_LABEL_KEYS = {
3041
3078
  };
3042
3079
  function ComposePanel(props) {
3043
3080
  const { engine, t, onError, store, state, loadEpoch, loadSettled, loadInFlight } = useHwpEditorContext();
3044
- const components = props.components ?? import_core16.STARTER_COMPONENTS;
3081
+ const components = props.components ?? import_core17.STARTER_COMPONENTS;
3045
3082
  const [preset, setPreset] = (0, import_react11.useState)("official");
3046
3083
  const [title, setTitle] = (0, import_react11.useState)("");
3047
3084
  const [author, setAuthor] = (0, import_react11.useState)("");
@@ -3163,7 +3200,7 @@ function ComposePanel(props) {
3163
3200
  const source = store.getState().document;
3164
3201
  try {
3165
3202
  const written = title.trim();
3166
- const spec = assembling ? (0, import_core16.documentSpecFromComponents)(
3203
+ const spec = assembling ? (0, import_core17.documentSpecFromComponents)(
3167
3204
  assembly.map((row) => ({ component: row.component, values: row.values })),
3168
3205
  written === "" ? {} : { title: written }
3169
3206
  ) : buildDocumentSpec(preset, { title, author, body });
@@ -3408,18 +3445,18 @@ function ComposePanel(props) {
3408
3445
 
3409
3446
  // src/ComponentsPanel.tsx
3410
3447
  var import_react12 = require("react");
3411
- var import_core17 = require("@hwp-editor/core");
3448
+ var import_core18 = require("@hwp-editor/core");
3412
3449
  var import_jsx_runtime12 = require("react/jsx-runtime");
3413
3450
  function componentsSupportReason(capabilities, t) {
3414
3451
  if (capabilities === null) return "";
3415
3452
  if (capabilities.editOps !== "v1") return t("components.disabledChannel");
3416
- if (!(0, import_core17.editReportSupported)(capabilities.version)) {
3417
- return t("components.disabledVersion", { version: import_core17.EDIT_REPORT_MIN_VERSION });
3453
+ if (!(0, import_core18.editReportSupported)(capabilities.version)) {
3454
+ return t("components.disabledVersion", { version: import_core18.EDIT_REPORT_MIN_VERSION });
3418
3455
  }
3419
3456
  return "";
3420
3457
  }
3421
3458
  function ComponentsPanel({
3422
- components = import_core17.STARTER_COMPONENTS,
3459
+ components = import_core18.STARTER_COMPONENTS,
3423
3460
  disabledReason = "",
3424
3461
  beforeDisabledReason = "",
3425
3462
  refusalFor,
@@ -3643,7 +3680,7 @@ function pendingTargetLabel(op, t, resolvers = {}, envelope = null) {
3643
3680
  }
3644
3681
 
3645
3682
  // src/messages.ts
3646
- var import_core18 = require("@hwp-editor/core");
3683
+ var import_core19 = require("@hwp-editor/core");
3647
3684
  var en = {
3648
3685
  // Toolbar
3649
3686
  "toolbar.toolsAria": "Editing tools",
@@ -3870,7 +3907,7 @@ var en = {
3870
3907
  "segment.rangeTextEditHint": "A character range formats text but cannot rewrite it. Use whole paragraph to edit its text.",
3871
3908
  "segment.runTextEditHint": "A run selection formats runs only, so the text controls are off. Select a paragraph to edit its text.",
3872
3909
  "segment.runSelectionParagraphs": (p) => p.count === 1 ? "Alignment and spacing apply to the 1 paragraph containing a selected run." : `Alignment and spacing apply to all ${p.count} paragraphs containing a selected run.`,
3873
- "segment.runSelectionTerminal": "Starts a new paragraph selection",
3910
+ "segment.runSelectionGestures": "Ctrl adds or removes a paragraph's runs; Shift extends by run.",
3874
3911
  "segment.noParagraphSelection": "This selection carries no paragraph the engine can address, so paragraph edits are off. Pick a paragraph from the list above.",
3875
3912
  "segment.cellSelectionRefused": "This selection is a table cell. Alignment and spacing apply to every paragraph in the cell, but replace, insert and delete address exactly one paragraph, and the engine refuses to delete a cell's last one. Pick a paragraph from the list above.",
3876
3913
  "segment.tableSelectionRefused": "This selection is a table, not a paragraph. The paragraph that holds the table also holds its own text, so editing or deleting it would take the table with it. Pick a paragraph from the list above.",
@@ -3886,6 +3923,8 @@ var en = {
3886
3923
  "segment.clickRefusedTable": "The click added nothing: a table has no paragraph of its own, so there is none to add. Click a paragraph or a cell.",
3887
3924
  "segment.clickRefusedContainer": "The click added nothing: an image, a field or a bookmark has no text of its own, so there is no paragraph to add. Click a paragraph or a cell.",
3888
3925
  "segment.clickRefusedUnaddressable": "That unit does not name a paragraph the engine can address, so the click selected nothing. Click a paragraph or a cell.",
3926
+ "segment.clickRefusedUnaddressableRuns": "The clicked paragraph or cell has runs, but the engine published no address for them, so the click selected nothing. Click without Ctrl or Shift to select the whole paragraph, or click a paragraph or cell whose runs have addresses.",
3927
+ "segment.clickRefusedNoRuns": "The clicked paragraph or cell has no runs to add. Click a paragraph or cell that has text.",
3889
3928
  "segment.selectSimilar": "Select content with matching direct formatting",
3890
3929
  "segment.similarChar": "Matching character format",
3891
3930
  "segment.similarPara": "Matching paragraph format",
@@ -4012,11 +4051,12 @@ var en = {
4012
4051
  "error.staleCompose": "The open document changed while the new document was being composed; compose again.",
4013
4052
  "error.stalePreview": "The document changed while this preview was being made; preview again on the open document.",
4014
4053
  "error.unmatchedReplace": "The engine found no text to replace in that paragraph, so nothing was applied. Select the text again and retry.",
4054
+ "error.unresolvableAddress": "The engine could not address the edit's paragraph or run. Runs inside some nested paragraphs cannot be addressed. Format text in a top-level paragraph or a table cell, or edit this target in a word processor.",
4015
4055
  "error.runRangeConflict": "A run format change was refused because an earlier pending change in that paragraph (text, a link, a field, a bookmark, an image or a cell) may change its text, so the run might no longer name the same characters. Nothing was applied. Remove the format change from the pending list, apply the rest, then format the run again; or queue the format change before the others.",
4016
4056
  "error.rangeTagRefused": "This HWP document holds a range mark the engine cannot move, so a pending edit was refused and nothing was applied. Edits that move text around the mark are refused: changing a paragraph's text length, changing a field's value to one of a different length, inserting or deleting a link, field, bookmark or image, or deleting a table. A table cell that holds a mark refuses any change to its content. The engine does not say which edit. Remove those pending edits from the pending list (a same-length text replacement in a paragraph, or a same-length field value, is accepted) and apply the rest. To make those edits, convert the document to HWPX, which drops the range marks, or remove the mark in Hangul, then reopen it; reopening drops the pending list.",
4017
- "error.stoppedWithoutDiagnostic": import_core18.STOPPED_WITHOUT_DIAGNOSTIC,
4057
+ "error.stoppedWithoutDiagnostic": import_core19.STOPPED_WITHOUT_DIAGNOSTIC,
4018
4058
  // The same rule for a failure with stderr but no Error: line (#140).
4019
- "error.noEngineCause": import_core18.NO_ENGINE_CAUSE,
4059
+ "error.noEngineCause": import_core19.NO_ENGINE_CAUSE,
4020
4060
  "error.badEnvelope": (p) => `The host returned a document read this editor cannot use: ${p.detail}. Have the host emit segment-envelope-v2 data, then reload.`,
4021
4061
  "error.readOnlyReplacement": "The editor became read-only before the template could be applied; switch to an editable document and retry.",
4022
4062
  "error.pendingReplacement": "Pending edits would be lost; apply or clear them before replacing this document.",
@@ -4092,14 +4132,14 @@ var en = {
4092
4132
  "image.insert": "Insert image",
4093
4133
  "image.delete": "Delete image",
4094
4134
  "image.file": "PNG/JPEG image file",
4095
- "image.fileHint": `Choose a PNG or JPEG image up to ${import_core18.MAX_IMAGE_ASSET_MIB} MiB.`,
4135
+ "image.fileHint": `Choose a PNG or JPEG image up to ${import_core19.MAX_IMAGE_ASSET_MIB} MiB.`,
4096
4136
  "image.unsupported": "Image upload needs a host with an upload command. With the agent-hwp engine, use 1.4.0 or newer, the version this editor verifies uploads against. A supplied engine has to advertise image-asset uploads. The desktop app has no upload command, and this editor cannot insert a picture from a local file, so insert the picture in a word processor and reopen the document here.",
4097
4137
  "image.anchor": "Insert after paragraph text",
4098
4138
  "image.deleteAnchor": "Image anchor text to delete",
4099
4139
  "image.width": "Width (mm)",
4100
4140
  "image.height": "Height (mm)",
4101
4141
  "image.reading": "Reading image...",
4102
- "image.invalidSize": `The image is empty or exceeds ${import_core18.MAX_IMAGE_ASSET_MIB} MiB; choose a smaller PNG/JPEG file.`,
4142
+ "image.invalidSize": `The image is empty or exceeds ${import_core19.MAX_IMAGE_ASSET_MIB} MiB; choose a smaller PNG/JPEG file.`,
4103
4143
  "image.invalidType": "The file is not PNG/JPEG image data; choose a supported image.",
4104
4144
  "image.readError": "The image could not be read; select the file again.",
4105
4145
  "error.prefix.image": "Image operation failed",
@@ -4341,7 +4381,7 @@ var ko = {
4341
4381
  "segment.rangeTextEditHint": "\uAE00\uC790 \uBC94\uC704 \uC120\uD0DD\uC740 \uC11C\uC2DD\uB9CC \uBC14\uAFC0 \uC218 \uC788\uACE0 \uD14D\uC2A4\uD2B8\uB294 \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uB97C \uD3B8\uC9D1\uD558\uB824\uBA74 \uBB38\uB2E8 \uC804\uCCB4\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
4342
4382
  "segment.runTextEditHint": "run \uC120\uD0DD\uC740 \uC11C\uC2DD\uB9CC \uBC14\uAFC0 \uC218 \uC788\uC5B4 \uD14D\uC2A4\uD2B8 \uC785\uB825\uB780\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uB97C \uD3B8\uC9D1\uD558\uB824\uBA74 \uBB38\uB2E8\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
4343
4383
  "segment.runSelectionParagraphs": (p) => `\uBB38\uB2E8 \uC815\uB82C\uACFC \uAC04\uACA9\uC740 \uC120\uD0DD\uD55C run\uC774 \uC788\uB294 \uBB38\uB2E8 ${p.count}\uAC1C \uC804\uCCB4\uC5D0 \uC801\uC6A9\uB429\uB2C8\uB2E4.`,
4344
- "segment.runSelectionTerminal": "\uC0C8 \uBB38\uB2E8 \uC120\uD0DD\uC774 \uC2DC\uC791\uB429\uB2C8\uB2E4",
4384
+ "segment.runSelectionGestures": "Ctrl\uB85C \uBB38\uB2E8\uC758 run\uC744 \uB354\uD558\uAC70\uB098 \uBE7C\uACE0, Shift\uB85C run \uB2E8\uC704\uB85C \uB113\uD799\uB2C8\uB2E4.",
4345
4385
  "segment.noParagraphSelection": "\uC774 \uC120\uD0DD\uC5D0\uB294 \uC5D4\uC9C4\uC774 \uC8FC\uC18C\uB85C \uC9C0\uC815\uD560 \uC218 \uC788\uB294 \uBB38\uB2E8\uC774 \uC5C6\uC5B4 \uBB38\uB2E8 \uD3B8\uC9D1\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC704 \uBAA9\uB85D\uC5D0\uC11C \uBB38\uB2E8\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
4346
4386
  "segment.cellSelectionRefused": "\uC774 \uC120\uD0DD\uC740 \uD45C\uC758 \uC140\uC785\uB2C8\uB2E4. \uC815\uB82C\uACFC \uBB38\uB2E8 \uBAA8\uC591\uC740 \uC140 \uC548\uC758 \uBAA8\uB4E0 \uBB38\uB2E8\uC5D0 \uC801\uC6A9\uB418\uC9C0\uB9CC, \uAD50\uCCB4, \uC0BD\uC785, \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uD558\uB098\uB9CC \uB300\uC0C1\uC73C\uB85C \uD558\uBA70, \uC5D4\uC9C4\uC740 \uC140\uC758 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8 \uC0AD\uC81C\uB3C4 \uAC70\uBD80\uD569\uB2C8\uB2E4. \uC704 \uBAA9\uB85D\uC5D0\uC11C \uBB38\uB2E8\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
4347
4387
  "segment.tableSelectionRefused": "\uC774 \uC120\uD0DD\uC740 \uBB38\uB2E8\uC774 \uC544\uB2C8\uB77C \uD45C\uC785\uB2C8\uB2E4. \uD45C\uB97C \uB2F4\uACE0 \uC788\uB294 \uBB38\uB2E8\uC740 \uC790\uAE30 \uD14D\uC2A4\uD2B8\uB3C4 \uD568\uAED8 \uB2F4\uACE0 \uC788\uC5B4, \uADF8 \uBB38\uB2E8\uC744 \uD3B8\uC9D1\uD558\uAC70\uB098 \uC0AD\uC81C\uD558\uBA74 \uD45C\uAC00 \uC0AC\uB77C\uC9D1\uB2C8\uB2E4. \uC704 \uBAA9\uB85D\uC5D0\uC11C \uBB38\uB2E8\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
@@ -4350,6 +4390,8 @@ var ko = {
4350
4390
  "segment.clickRefusedTable": "\uD074\uB9AD\uC73C\uB85C \uCD94\uAC00\uB41C \uBB38\uB2E8\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uD45C\uB294 \uC790\uCCB4 \uBB38\uB2E8\uC774 \uC5C6\uC5B4 \uCD94\uAC00\uD560 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4351
4391
  "segment.clickRefusedContainer": "\uD074\uB9AD\uC73C\uB85C \uCD94\uAC00\uB41C \uBB38\uB2E8\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uADF8\uB9BC, \uD544\uB4DC \uB610\uB294 \uCC45\uAC08\uD53C\uB294 \uC790\uCCB4 \uD14D\uC2A4\uD2B8\uAC00 \uC5C6\uC5B4 \uCD94\uAC00\uD560 \uBB38\uB2E8\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4352
4392
  "segment.clickRefusedUnaddressable": "\uADF8 \uC694\uC18C\uB294 \uC5D4\uC9C4\uC774 \uC8FC\uC18C\uB85C \uC9C0\uC815\uD560 \uC218 \uC788\uB294 \uBB38\uB2E8\uC744 \uAC00\uB9AC\uD0A4\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uD074\uB9AD\uC73C\uB85C \uC120\uD0DD\uB41C \uAC83\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4393
+ "segment.clickRefusedUnaddressableRuns": "\uD074\uB9AD\uD55C \uBB38\uB2E8\uC774\uB098 \uC140\uC5D0 run\uC740 \uC788\uC9C0\uB9CC \uC5D4\uC9C4\uC774 \uADF8 run\uC758 \uC8FC\uC18C\uB97C \uC81C\uACF5\uD558\uC9C0 \uC54A\uC544 \uC120\uD0DD\uB41C \uAC83\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. Ctrl\uC774\uB098 Shift \uC5C6\uC774 \uD074\uB9AD\uD574 \uBB38\uB2E8 \uC804\uCCB4\uB97C \uC120\uD0DD\uD558\uAC70\uB098, \uC8FC\uC18C\uAC00 \uC788\uB294 run\uC744 \uAC00\uC9C4 \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4394
+ "segment.clickRefusedNoRuns": "\uD074\uB9AD\uD55C \uBB38\uB2E8\uC774\uB098 \uC140\uC5D0 \uCD94\uAC00\uD560 run\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uAC00 \uC788\uB294 \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4353
4395
  "segment.selectSimilar": "\uC9C1\uC811 \uC11C\uC2DD\uC774 \uAC19\uC740 \uB0B4\uC6A9 \uC120\uD0DD",
4354
4396
  "segment.similarChar": "\uAC19\uC740 \uAE00\uC790 \uC11C\uC2DD",
4355
4397
  "segment.similarPara": "\uAC19\uC740 \uBB38\uB2E8 \uC11C\uC2DD",
@@ -4467,6 +4509,7 @@ var ko = {
4467
4509
  "error.staleCompose": "\uC0C8 \uBB38\uC11C\uB97C \uC791\uC131\uD558\uB294 \uB3D9\uC548 \uC5F4\uB9B0 \uBB38\uC11C\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC791\uC131\uD558\uC138\uC694.",
4468
4510
  "error.stalePreview": "\uBBF8\uB9AC\uBCF4\uAE30\uB97C \uB9CC\uB4DC\uB294 \uB3D9\uC548 \uBB38\uC11C\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uC5F4\uB9B0 \uBB38\uC11C\uC5D0\uC11C \uB2E4\uC2DC \uBBF8\uB9AC\uBCF4\uAE30\uD558\uC138\uC694.",
4469
4511
  "error.unmatchedReplace": "\uD574\uB2F9 \uBB38\uB2E8\uC5D0\uC11C \uBC14\uAFC0 \uD14D\uC2A4\uD2B8\uB97C \uC5D4\uC9C4\uC774 \uCC3E\uC9C0 \uBABB\uD574 \uC544\uBB34\uAC83\uB3C4 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uB97C \uB2E4\uC2DC \uC120\uD0DD\uD55C \uB4A4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694.",
4512
+ "error.unresolvableAddress": "\uC5D4\uC9C4\uC774 \uD3B8\uC9D1 \uB300\uC0C1 \uBB38\uB2E8\uC774\uB098 run\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC77C\uBD80 \uC911\uCCA9 \uBB38\uB2E8 \uC548\uC758 run\uC740 \uC5D4\uC9C4\uC774 \uC8FC\uC18C\uB85C \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uCD5C\uC0C1\uC704 \uBB38\uB2E8\uC774\uB098 \uD45C \uC140\uC758 \uD14D\uC2A4\uD2B8\uC5D0 \uC11C\uC2DD\uC744 \uC9C0\uC815\uD558\uAC70\uB098, \uC774 \uB300\uC0C1\uC740 \uC6CC\uB4DC\uD504\uB85C\uC138\uC11C\uC5D0\uC11C \uD3B8\uC9D1\uD558\uC138\uC694.",
4470
4513
  "error.runRangeConflict": "\uAC19\uC740 \uBB38\uB2E8\uC5D0 \uB300\uAE30 \uC911\uC778 \uC55E\uC120 \uBCC0\uACBD(\uD14D\uC2A4\uD2B8, \uB9C1\uD06C, \uD544\uB4DC, \uCC45\uAC08\uD53C, \uC774\uBBF8\uC9C0, \uC140)\uC774 \uBB38\uB2E8 \uD14D\uC2A4\uD2B8\uB97C \uBC14\uAFC0 \uC218 \uC788\uC5B4 run\uC774 \uB354 \uC774\uC0C1 \uAC19\uC740 \uAE00\uC790\uB97C \uAC00\uB9AC\uD0A4\uC9C0 \uC54A\uC744 \uC218 \uC788\uC73C\uBBC0\uB85C run \uC11C\uC2DD \uBCC0\uACBD\uC774 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC544\uBB34\uAC83\uB3C4 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uB300\uAE30 \uBAA9\uB85D\uC5D0\uC11C \uC11C\uC2DD \uBCC0\uACBD\uC744 \uC81C\uAC70\uD558\uACE0 \uB098\uBA38\uC9C0\uB97C \uC801\uC6A9\uD55C \uB4A4 run \uC11C\uC2DD\uC744 \uB2E4\uC2DC \uC9C0\uC815\uD558\uAC70\uB098, \uC11C\uC2DD \uBCC0\uACBD\uC744 \uB2E4\uB978 \uBCC0\uACBD\uBCF4\uB2E4 \uBA3C\uC800 \uB300\uAE30\uC5F4\uC5D0 \uB123\uC73C\uC138\uC694.",
4471
4514
  "error.rangeTagRefused": "\uC774 HWP \uBB38\uC11C\uC5D0\uB294 \uC5D4\uC9C4\uC774 \uC62E\uAE38 \uC218 \uC5C6\uB294 \uAD6C\uAC04 \uD45C\uC2DC\uAC00 \uC788\uC5B4 \uB300\uAE30 \uC911\uC778 \uD3B8\uC9D1\uC774 \uAC70\uBD80\uB418\uC5C8\uACE0, \uC544\uBB34\uAC83\uB3C4 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uAD6C\uAC04 \uD45C\uC2DC \uC8FC\uBCC0\uC758 \uAE00\uC790\uB97C \uC6C0\uC9C1\uC774\uB294 \uD3B8\uC9D1\uC740 \uAC70\uBD80\uB429\uB2C8\uB2E4: \uBB38\uB2E8 \uD14D\uC2A4\uD2B8 \uAE38\uC774 \uBCC0\uACBD, \uAE38\uC774\uAC00 \uB2E4\uB978 \uD544\uB4DC \uAC12\uC73C\uB85C\uC758 \uBCC0\uACBD, \uB9C1\uD06C\xB7\uD544\uB4DC\xB7\uCC45\uAC08\uD53C\xB7\uC774\uBBF8\uC9C0\uC758 \uC0BD\uC785\uC774\uB098 \uC0AD\uC81C, \uD45C \uC0AD\uC81C. \uAD6C\uAC04 \uD45C\uC2DC\uAC00 \uC788\uB294 \uD45C \uC140\uC740 \uB0B4\uC6A9\uC744 \uC5B4\uB5BB\uAC8C \uBC14\uAFB8\uB4E0 \uAC70\uBD80\uD569\uB2C8\uB2E4. \uC5D4\uC9C4\uC740 \uC5B4\uB290 \uD3B8\uC9D1\uC778\uC9C0 \uC54C\uB824 \uC8FC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uB300\uAE30 \uBAA9\uB85D\uC5D0\uC11C \uD574\uB2F9 \uD3B8\uC9D1\uC744 \uC81C\uAC70\uD558\uACE0(\uBB38\uB2E8 \uC548\uC758 \uAC19\uC740 \uAE38\uC774 \uD14D\uC2A4\uD2B8 \uCE58\uD658\uACFC \uAC19\uC740 \uAE38\uC774\uC758 \uD544\uB4DC \uAC12\uC740 \uD5C8\uC6A9\uB429\uB2C8\uB2E4) \uB098\uBA38\uC9C0\uB97C \uC801\uC6A9\uD558\uC138\uC694. \uADF8\uB7F0 \uD3B8\uC9D1\uC774 \uD544\uC694\uD558\uBA74 \uBB38\uC11C\uB97C HWPX\uB85C \uBCC0\uD658\uD558\uAC70\uB098(\uBCC0\uD658\uD558\uBA74 \uAD6C\uAC04 \uD45C\uC2DC\uAC00 \uC0AC\uB77C\uC9D1\uB2C8\uB2E4) \uD55C\uAE00\uC5D0\uC11C \uAD6C\uAC04 \uD45C\uC2DC\uB97C \uC81C\uAC70\uD55C \uB4A4 \uB2E4\uC2DC \uC5EC\uC138\uC694. \uB2E4\uC2DC \uC5F4\uBA74 \uB300\uAE30 \uBAA9\uB85D\uC740 \uC0AC\uB77C\uC9D1\uB2C8\uB2E4.",
4472
4515
  "error.noEngineCause": "\uC5D4\uC9C4\uC774 \uC6D0\uC778\uC744 \uBC1D\uD788\uC9C0 \uC54A\uACE0 \uC2E4\uD328\uD588\uC73C\uBBC0\uB85C(\uB300\uAC1C \uBE44\uC815\uC0C1 \uC885\uB8CC\uB41C \uACBD\uC6B0\uC785\uB2C8\uB2E4) \uC544\uBB34\uAC83\uB3C4 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC801\uC6A9\uD558\uACE0, \uAC19\uC740 \uBC29\uC2DD\uC73C\uB85C \uC2E4\uD328\uD558\uBA74 \uD638\uC2A4\uD2B8 \uB85C\uADF8\uC640 \uD568\uAED8 \uC54C\uB824 \uC8FC\uC138\uC694.",
@@ -4546,14 +4589,14 @@ var ko = {
4546
4589
  "image.insert": "\uC774\uBBF8\uC9C0 \uC0BD\uC785",
4547
4590
  "image.delete": "\uC774\uBBF8\uC9C0 \uC0AD\uC81C",
4548
4591
  "image.file": "PNG/JPEG \uC774\uBBF8\uC9C0 \uD30C\uC77C",
4549
- "image.fileHint": `${import_core18.MAX_IMAGE_ASSET_MIB} MiB \uC774\uD558\uC758 PNG \uB610\uB294 JPEG \uC774\uBBF8\uC9C0\uB97C \uC120\uD0DD\uD558\uC138\uC694.`,
4592
+ "image.fileHint": `${import_core19.MAX_IMAGE_ASSET_MIB} MiB \uC774\uD558\uC758 PNG \uB610\uB294 JPEG \uC774\uBBF8\uC9C0\uB97C \uC120\uD0DD\uD558\uC138\uC694.`,
4550
4593
  "image.unsupported": "\uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC\uB294 \uC5C5\uB85C\uB4DC \uBA85\uB839\uC774 \uC788\uB294 \uD638\uC2A4\uD2B8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. agent-hwp \uC5D4\uC9C4\uC774\uB77C\uBA74 \uC774 \uD3B8\uC9D1\uAE30\uAC00 \uC5C5\uB85C\uB4DC\uB97C \uAC80\uC99D\uD55C 1.4.0 \uC774\uC0C1\uC744 \uC0AC\uC6A9\uD558\uC138\uC694. \uC5D4\uC9C4\uC744 \uC9C1\uC811 \uACF5\uAE09\uD558\uB294 \uD638\uC2A4\uD2B8\uB294 \uC774\uBBF8\uC9C0 \uC790\uC0B0 \uC5C5\uB85C\uB4DC\uB97C \uC9C0\uC6D0\uD55C\uB2E4\uACE0 \uC54C\uB824\uC57C \uD569\uB2C8\uB2E4. \uB370\uC2A4\uD06C\uD1B1 \uC571\uC5D0\uB294 \uC5C5\uB85C\uB4DC \uBA85\uB839\uC774 \uC5C6\uACE0 \uC774 \uD3B8\uC9D1\uAE30\uB294 \uB85C\uCEEC \uD30C\uC77C\uC5D0\uC11C \uADF8\uB9BC\uC744 \uB123\uC744 \uC218 \uC5C6\uC73C\uBBC0\uB85C, \uC6CC\uB4DC\uD504\uB85C\uC138\uC11C\uC5D0\uC11C \uADF8\uB9BC\uC744 \uB123\uC740 \uB4A4 \uC774 \uD3B8\uC9D1\uAE30\uC5D0\uC11C \uBB38\uC11C\uB97C \uB2E4\uC2DC \uC5EC\uC138\uC694.",
4551
4594
  "image.anchor": "\uC774 \uBB38\uB2E8 \uBB38\uC790\uC5F4 \uB4A4\uC5D0 \uC0BD\uC785",
4552
4595
  "image.deleteAnchor": "\uC0AD\uC81C\uD560 \uC774\uBBF8\uC9C0\uC758 \uC704\uCE58 \uBB38\uC790\uC5F4",
4553
4596
  "image.width": "\uB108\uBE44 (mm)",
4554
4597
  "image.height": "\uB192\uC774 (mm)",
4555
4598
  "image.reading": "\uC774\uBBF8\uC9C0 \uC77D\uB294 \uC911...",
4556
- "image.invalidSize": `\uC774\uBBF8\uC9C0\uAC00 \uBE44\uC5B4 \uC788\uAC70\uB098 ${import_core18.MAX_IMAGE_ASSET_MIB} MiB\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4. \uB354 \uC791\uC740 PNG/JPEG \uD30C\uC77C\uC744 \uC120\uD0DD\uD558\uC138\uC694.`,
4599
+ "image.invalidSize": `\uC774\uBBF8\uC9C0\uAC00 \uBE44\uC5B4 \uC788\uAC70\uB098 ${import_core19.MAX_IMAGE_ASSET_MIB} MiB\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4. \uB354 \uC791\uC740 PNG/JPEG \uD30C\uC77C\uC744 \uC120\uD0DD\uD558\uC138\uC694.`,
4557
4600
  "image.invalidType": "PNG/JPEG \uC774\uBBF8\uC9C0 \uB370\uC774\uD130\uAC00 \uC544\uB2D9\uB2C8\uB2E4. \uC9C0\uC6D0\uD558\uB294 \uC774\uBBF8\uC9C0 \uD30C\uC77C\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
4558
4601
  "image.readError": "\uC774\uBBF8\uC9C0\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD30C\uC77C\uC744 \uB2E4\uC2DC \uC120\uD0DD\uD558\uC138\uC694.",
4559
4602
  "error.prefix.image": "\uC774\uBBF8\uC9C0 \uC791\uC5C5 \uC2E4\uD328",
@@ -4616,16 +4659,16 @@ function selectionParagraphs(envelope, state) {
4616
4659
  const seen = /* @__PURE__ */ new Set();
4617
4660
  const paragraphs = [];
4618
4661
  for (const ref of refs) {
4619
- const segment = (0, import_core19.segmentAtRef)(envelope, ref);
4662
+ const segment = (0, import_core20.segmentAtRef)(envelope, ref);
4620
4663
  if (segment === void 0) continue;
4621
- for (const paragraph of (0, import_core19.resolveSelection)(envelope, segment).paragraphs) {
4664
+ for (const paragraph of (0, import_core20.resolveSelection)(envelope, segment).paragraphs) {
4622
4665
  const key = paragraph.id ?? `${paragraph.section}:${paragraph.para}:${paragraph.start}`;
4623
4666
  if (seen.has(key)) continue;
4624
4667
  seen.add(key);
4625
4668
  paragraphs.push(paragraph);
4626
4669
  }
4627
4670
  }
4628
- return paragraphs.sort(import_core19.compareSegments);
4671
+ return paragraphs.sort(import_core20.compareSegments);
4629
4672
  }
4630
4673
  function insertAnchor(covered, position) {
4631
4674
  const paragraph = position === "before" ? covered[0] : covered[covered.length - 1];
@@ -4644,7 +4687,8 @@ var RENDER_SVG = { format: "svg" };
4644
4687
  var REFUSAL_MESSAGE = {
4645
4688
  unmatched_replace: "error.unmatchedReplace",
4646
4689
  run_range_conflict: "error.runRangeConflict",
4647
- range_tag: "error.rangeTagRefused"
4690
+ range_tag: "error.rangeTagRefused",
4691
+ unresolvable_address: "error.unresolvableAddress"
4648
4692
  };
4649
4693
  var HwpEditor = (0, import_react13.forwardRef)(
4650
4694
  function HwpEditor2(props, ref) {
@@ -4662,7 +4706,7 @@ var HwpEditor = (0, import_react13.forwardRef)(
4662
4706
  className,
4663
4707
  style
4664
4708
  } = props;
4665
- const catalogue = components ?? import_core19.STARTER_COMPONENTS;
4709
+ const catalogue = components ?? import_core20.STARTER_COMPONENTS;
4666
4710
  const cbRef = (0, import_react13.useRef)({ onReady, onError, onChange, onDirtyChange });
4667
4711
  (0, import_react13.useEffect)(() => {
4668
4712
  cbRef.current = { onReady, onError, onChange, onDirtyChange };
@@ -4680,7 +4724,7 @@ var HwpEditor = (0, import_react13.forwardRef)(
4680
4724
  cbRef.current.onDirtyChange?.(dirty2);
4681
4725
  }, []);
4682
4726
  const t = (0, import_react13.useMemo)(() => createT(locale, messages), [locale, messages]);
4683
- const [store] = (0, import_react13.useState)(() => (0, import_core19.createStore)());
4727
+ const [store] = (0, import_react13.useState)(() => (0, import_core20.createStore)());
4684
4728
  const state = (0, import_react13.useSyncExternalStore)(store.subscribe, store.getState, store.getState);
4685
4729
  const [envelope, setEnvelope] = (0, import_react13.useState)(null);
4686
4730
  const [capabilities, setCapabilities] = (0, import_react13.useState)(null);
@@ -4733,9 +4777,9 @@ var HwpEditor = (0, import_react13.forwardRef)(
4733
4777
  const readEnvelope = (0, import_react13.useCallback)(async (target) => {
4734
4778
  const read = await engine.read(target);
4735
4779
  try {
4736
- return (0, import_core19.validateCatEnvelope)(read);
4780
+ return (0, import_core20.validateCatEnvelope)(read);
4737
4781
  } catch (e) {
4738
- throw new import_core19.HwpEngineError(
4782
+ throw new import_core20.HwpEngineError(
4739
4783
  "failed",
4740
4784
  latestT.current("error.badEnvelope", { detail: envelopeProblem(e instanceof Error ? e.message : String(e)) }),
4741
4785
  { cause: e }
@@ -4754,7 +4798,7 @@ var HwpEditor = (0, import_react13.forwardRef)(
4754
4798
  insertedTriedFor.current = envelope;
4755
4799
  const hidden = [];
4756
4800
  const paragraphs = pendingInserted.flatMap((id) => {
4757
- const segment = (0, import_core19.segmentAtRef)(envelope, { id, section: 0, para: 0 });
4801
+ const segment = (0, import_core20.segmentAtRef)(envelope, { id, section: 0, para: 0 });
4758
4802
  if (segment === void 0) hidden.push(id);
4759
4803
  return segment === void 0 || segment.kind !== "para" ? [] : [segment];
4760
4804
  });
@@ -4762,9 +4806,9 @@ var HwpEditor = (0, import_react13.forwardRef)(
4762
4806
  if (paragraphs.length === 0) return;
4763
4807
  store.dispatch({
4764
4808
  type: "selectMany",
4765
- selections: paragraphs.map(import_core19.segmentRef),
4766
- primary: (0, import_core19.segmentRef)(paragraphs[0]),
4767
- anchor: (0, import_core19.segmentRef)(paragraphs[0])
4809
+ selections: paragraphs.map(import_core20.segmentRef),
4810
+ primary: (0, import_core20.segmentRef)(paragraphs[0]),
4811
+ anchor: (0, import_core20.segmentRef)(paragraphs[0])
4768
4812
  });
4769
4813
  setInsertedHidden(hidden.length === 0 ? null : { envelope, selections: store.getState().selections, ids: hidden });
4770
4814
  }, [envelope, pendingInserted, store]);
@@ -4845,22 +4889,22 @@ var HwpEditor = (0, import_react13.forwardRef)(
4845
4889
  const queueImage = (0, import_react13.useCallback)((op, asset) => {
4846
4890
  const snapshot = store.getState();
4847
4891
  if (!editable || envelope === null || snapshot.document === null || snapshot.status === "applying") return;
4848
- if (capabilities?.imageAssets !== "v1") throw new import_core19.HwpEngineError("unavailable", t("image.unsupported"));
4849
- if (op.path !== asset.name) throw new import_core19.HwpEngineError("bad_request", t("image.referenceMismatch"));
4892
+ if (capabilities?.imageAssets !== "v1") throw new import_core20.HwpEngineError("unavailable", t("image.unsupported"));
4893
+ if (op.path !== asset.name) throw new import_core20.HwpEngineError("bad_request", t("image.referenceMismatch"));
4850
4894
  const active = snapshot.pendingOps.flatMap((pending) => {
4851
4895
  const stored = imageAssets.current.get(pending);
4852
4896
  return stored === void 0 ? [] : [stored];
4853
4897
  });
4854
- if (active.length + 1 > import_core19.MAX_IMAGE_ASSETS || active.reduce((bytes, image) => bytes + image.data.length, asset.data.length) > import_core19.MAX_IMAGE_ASSETS_TOTAL_BYTES) {
4855
- throw new import_core19.HwpEngineError("bad_request", t("image.queueLimit", { count: import_core19.MAX_IMAGE_ASSETS, miB: import_core19.MAX_IMAGE_ASSETS_TOTAL_BYTES / (1024 * 1024) }));
4898
+ if (active.length + 1 > import_core20.MAX_IMAGE_ASSETS || active.reduce((bytes, image) => bytes + image.data.length, asset.data.length) > import_core20.MAX_IMAGE_ASSETS_TOTAL_BYTES) {
4899
+ throw new import_core20.HwpEngineError("bad_request", t("image.queueLimit", { count: import_core20.MAX_IMAGE_ASSETS, miB: import_core20.MAX_IMAGE_ASSETS_TOTAL_BYTES / (1024 * 1024) }));
4856
4900
  }
4857
- (0, import_core19.validateImageAssets)([...active, asset]);
4901
+ (0, import_core20.validateImageAssets)([...active, asset]);
4858
4902
  imageAssets.current.set(op, { name: asset.name, data: asset.data.slice() });
4859
4903
  store.dispatch({ type: "queueOp", op });
4860
4904
  }, [editable, envelope, capabilities, store, t]);
4861
4905
  (0, import_react13.useEffect)(() => {
4862
4906
  if (envelope === null || state.selection === null) return;
4863
- const segment = (0, import_core19.segmentAtRef)(envelope, state.selection);
4907
+ const segment = (0, import_core20.segmentAtRef)(envelope, state.selection);
4864
4908
  if (segment === void 0) return;
4865
4909
  const next = hostsTable(envelope, segment) ? "table" : "para";
4866
4910
  setTab((current) => current === "components" || current === "styles" ? current : next);
@@ -5007,17 +5051,17 @@ var HwpEditor = (0, import_react13.forwardRef)(
5007
5051
  noteRefusal(t("error.loadingReplacement"));
5008
5052
  return;
5009
5053
  }
5010
- const segment = snapshot.selection === null ? void 0 : (0, import_core19.segmentAtRef)(envelope, snapshot.selection);
5054
+ const segment = snapshot.selection === null ? void 0 : (0, import_core20.segmentAtRef)(envelope, snapshot.selection);
5011
5055
  const covered = selectionParagraphs(envelope, snapshot);
5012
5056
  const address = segment === void 0 ? null : insertAnchor(covered, position);
5013
5057
  if (address === null) return;
5014
- if (position === "before" && covered[0] !== void 0 && (0, import_core19.isSectionDefinitionParagraph)(covered[0])) return;
5058
+ if (position === "before" && covered[0] !== void 0 && (0, import_core20.isSectionDefinitionParagraph)(covered[0])) return;
5015
5059
  let blocks;
5016
5060
  let styles2;
5017
5061
  try {
5018
- const filled = (0, import_core19.substituteParams)(component, values, component.name);
5062
+ const filled = (0, import_core20.substituteParams)(component, values, component.name);
5019
5063
  blocks = filled.blocks;
5020
- styles2 = (0, import_core19.componentStyleMap)([filled]);
5064
+ styles2 = (0, import_core20.componentStyleMap)([filled]);
5021
5065
  } catch (error) {
5022
5066
  const shown = toError(componentFailure(error, t));
5023
5067
  noteNotice(shown.message, shown);
@@ -5070,7 +5114,7 @@ var HwpEditor = (0, import_react13.forwardRef)(
5070
5114
  }
5071
5115
  let ids;
5072
5116
  try {
5073
- ids = (0, import_core19.insertedParagraphIds)(outcome);
5117
+ ids = (0, import_core20.insertedParagraphIds)(outcome);
5074
5118
  } catch (error) {
5075
5119
  noteNotice(toError(error).message, error);
5076
5120
  return;
@@ -5081,12 +5125,12 @@ var HwpEditor = (0, import_react13.forwardRef)(
5081
5125
  [emitError, engine, envelope, noteRefusal, replaceDocument, store, t]
5082
5126
  );
5083
5127
  const selectedTargets = envelope === null || state.selection === null ? void 0 : (() => {
5084
- const segment = (0, import_core19.segmentAtRef)(envelope, state.selection);
5085
- return segment === void 0 ? void 0 : (0, import_core19.resolveSelection)(envelope, segment);
5128
+ const segment = (0, import_core20.segmentAtRef)(envelope, state.selection);
5129
+ return segment === void 0 ? void 0 : (0, import_core20.resolveSelection)(envelope, segment);
5086
5130
  })();
5087
5131
  const componentAnchor = envelope === null || state.selection === null ? [] : selectionParagraphs(envelope, state);
5088
5132
  const selectedAddress = insertAnchor(componentAnchor, "after");
5089
- const beforeDisabledReason = componentAnchor[0] !== void 0 && (0, import_core19.isSectionDefinitionParagraph)(componentAnchor[0]) ? t("components.positionBeforeLocked") : "";
5133
+ const beforeDisabledReason = componentAnchor[0] !== void 0 && (0, import_core20.isSectionDefinitionParagraph)(componentAnchor[0]) ? t("components.positionBeforeLocked") : "";
5090
5134
  const componentDisabled = componentsSupportReason(capabilities, t) || (state.pendingOps.length > 0 ? t("components.pendingEdits") : "") || (state.selection === null ? t("components.noSelection") : selectedAddress !== null ? "" : selectedTargets?.refusal === "cell" ? t("segment.cellSelectionRefused") : selectedTargets?.refusal === "table" ? t("segment.tableSelectionRefused") : selectedTargets?.refusal === "container" ? t("segment.containerSelectionRefused") : t("components.noAddress"));
5091
5135
  const componentRefusal = (0, import_react13.useCallback)(
5092
5136
  (component) => insertionPreflight(component.blocks, state.document?.name ?? "", t),
@@ -5513,6 +5557,31 @@ var HwpEditor = (0, import_react13.forwardRef)(
5513
5557
  ) });
5514
5558
  }
5515
5559
  );
5560
+
5561
+ // src/sanitize.ts
5562
+ function sanitizeSvg(svgText) {
5563
+ const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
5564
+ const root = doc.documentElement;
5565
+ if (root === null || root.nodeName === "parsererror" || doc.getElementsByTagName("parsererror").length > 0) {
5566
+ return "";
5567
+ }
5568
+ const dangerous = root.querySelectorAll("script, foreignObject");
5569
+ for (const node of Array.from(dangerous)) node.remove();
5570
+ scrubElement(root);
5571
+ return root.outerHTML;
5572
+ }
5573
+ function scrubElement(el) {
5574
+ for (const attr of Array.from(el.attributes)) {
5575
+ const name = attr.name.toLowerCase();
5576
+ const value = attr.value.trim().toLowerCase();
5577
+ if (name.startsWith("on")) {
5578
+ el.removeAttribute(attr.name);
5579
+ } else if ((name === "href" || name === "xlink:href") && value.startsWith("javascript:")) {
5580
+ el.removeAttribute(attr.name);
5581
+ }
5582
+ }
5583
+ for (const child of Array.from(el.children)) scrubElement(child);
5584
+ }
5516
5585
  // Annotate the CommonJS export names for ESM import in node:
5517
5586
  0 && (module.exports = {
5518
5587
  COMPOSE_PRESETS,