document-cli 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1194,7 +1194,7 @@ function registerSetMetadataCommand(program) {
1194
1194
  }
1195
1195
  //#endregion
1196
1196
  //#region package.json
1197
- var version = "1.5.0";
1197
+ var version = "1.6.0";
1198
1198
  //#endregion
1199
1199
  //#region src/program.ts
1200
1200
  function createProgram() {
@@ -1221,7 +1221,7 @@ function createProgram() {
1221
1221
  //#region src/cli.ts
1222
1222
  async function launchTui(startPath, signal) {
1223
1223
  try {
1224
- const { runTui } = await import("./tui-B-o3iI9a.js");
1224
+ const { runTui } = await import("./tui-CqYlr4nN.js");
1225
1225
  await runTui({
1226
1226
  startPath,
1227
1227
  signal
package/dist/index.cjs CHANGED
@@ -1661,7 +1661,7 @@ function registerSetMetadataCommand(program) {
1661
1661
  }
1662
1662
  //#endregion
1663
1663
  //#region package.json
1664
- var version = "1.5.0";
1664
+ var version = "1.6.0";
1665
1665
  //#endregion
1666
1666
  //#region src/program.ts
1667
1667
  function createProgram() {
package/dist/index.js CHANGED
@@ -1660,7 +1660,7 @@ function registerSetMetadataCommand(program) {
1660
1660
  }
1661
1661
  //#endregion
1662
1662
  //#region package.json
1663
- var version = "1.5.0";
1663
+ var version = "1.6.0";
1664
1664
  //#endregion
1665
1665
  //#region src/program.ts
1666
1666
  function createProgram() {
@@ -490,10 +490,6 @@ function shapeAt(doc, containerIndex, shapeIndex) {
490
490
  if (doc.format === "odg") return doc.editor.pages()[containerIndex]?.shapes()[shapeIndex];
491
491
  return doc.editor.slides()[containerIndex]?.shapes()[shapeIndex];
492
492
  }
493
- function rotatableShapeAt(doc, containerIndex, shapeIndex) {
494
- if (doc.format === "odg") return doc.editor.pages()[containerIndex]?.shapes()[shapeIndex];
495
- return doc.editor.slides()[containerIndex]?.shapes()[shapeIndex];
496
- }
497
493
  function sheetAt(doc, sheetIndex) {
498
494
  return doc.editor.sheets()[sheetIndex];
499
495
  }
@@ -526,8 +522,8 @@ function withSheet(state, sheetIndex, apply) {
526
522
  apply(sheet);
527
523
  });
528
524
  }
529
- function setCellText(cell, text) {
530
- const paragraph = cell.paragraphs()[0] ?? cell.appendParagraph();
525
+ function setTextContainerText(container, text) {
526
+ const paragraph = container.paragraphs()[0] ?? container.appendParagraph();
531
527
  const runs = paragraph.runs();
532
528
  const firstRun = runs[0];
533
529
  if (firstRun === void 0) {
@@ -537,6 +533,9 @@ function setCellText(cell, text) {
537
533
  firstRun.text = text;
538
534
  for (const extra of runs.slice(1)) extra.remove();
539
535
  }
536
+ function setCellText(cell, text) {
537
+ setTextContainerText(cell, text);
538
+ }
540
539
  function appReducer(state, action) {
541
540
  switch (action.type) {
542
541
  case "PUSH_SCREEN": return {
@@ -721,6 +720,18 @@ function appReducer(state, action) {
721
720
  appended.list = membership;
722
721
  });
723
722
  }
723
+ case "SET_LIST_ITEM_TEXT": {
724
+ const doc = wordprocessingDocument(state);
725
+ if (doc === void 0) return wrongDocument(state, "a docx or odt document");
726
+ if (doc.format !== "odt") return wrongDocument(state, "an odt document (lists are an odt-only concept)");
727
+ const list = doc.editor.lists()[action.blockIndex];
728
+ if (list === void 0) return withStatus(state, "warning", `There is no list at index ${action.blockIndex}`);
729
+ const item = list.items()[action.itemIndex];
730
+ if (item === void 0) return withStatus(state, "warning", `List ${action.blockIndex} has no item at index ${action.itemIndex}`);
731
+ return mutate(state, doc, () => {
732
+ setTextContainerText(item, action.text);
733
+ });
734
+ }
724
735
  case "ADD_SLIDE": {
725
736
  const doc = presentationDocument(state);
726
737
  if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
@@ -793,16 +804,9 @@ function appReducer(state, action) {
793
804
  case "SET_SHAPE_FRAME": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
794
805
  shape.frame = action.frame;
795
806
  });
796
- case "SET_SHAPE_ROTATION": {
797
- const doc = shapeHostDocument(state);
798
- if (doc === void 0) return wrongDocument(state, "a pptx, odp or odg document");
799
- if (doc.format === "pptx") return withStatus(state, "warning", "documents.js has no rotation setter for a pptx shape; rotate the shape in odp instead");
800
- const shape = rotatableShapeAt(doc, action.containerIndex, action.shapeIndex);
801
- if (shape === void 0) return withStatus(state, "warning", `There is no shape ${action.shapeIndex} at index ${action.containerIndex}`);
802
- return mutate(state, doc, () => {
803
- shape.rotationDeg = action.rotationDeg;
804
- });
805
- }
807
+ case "SET_SHAPE_ROTATION": return withShape(state, action.containerIndex, action.shapeIndex, (shape) => {
808
+ shape.rotationDeg = action.rotationDeg;
809
+ });
806
810
  case "SET_SLIDE_NOTES": {
807
811
  const doc = presentationDocument(state);
808
812
  if (doc === void 0) return wrongDocument(state, "a pptx or odp document");
@@ -1020,6 +1024,11 @@ const COMMANDS = [
1020
1024
  usage: ":close",
1021
1025
  description: "Close the open document"
1022
1026
  },
1027
+ {
1028
+ name: "undo",
1029
+ usage: ":undo",
1030
+ description: "Undo the last change"
1031
+ },
1023
1032
  {
1024
1033
  name: "help",
1025
1034
  usage: ":help",
@@ -1152,6 +1161,9 @@ async function runCommand(line, state, dispatch) {
1152
1161
  case "close":
1153
1162
  dispatch({ type: "REQUEST_CLOSE" });
1154
1163
  return;
1164
+ case "undo":
1165
+ dispatch({ type: "UNDO" });
1166
+ return;
1155
1167
  case "help":
1156
1168
  dispatch({
1157
1169
  type: "OPEN_OVERLAY",
@@ -1430,6 +1442,10 @@ const GLOBAL_KEYS = [
1430
1442
  keys: "Ctrl+W",
1431
1443
  description: "Close the open document"
1432
1444
  },
1445
+ {
1446
+ keys: "Ctrl+Z",
1447
+ description: "Undo the last change"
1448
+ },
1433
1449
  {
1434
1450
  keys: "q / Ctrl+C",
1435
1451
  description: "Quit"
@@ -3019,6 +3035,10 @@ function requireShapeOrVectorDetailScreen(state) {
3019
3035
  if (screen.kind !== "shapeOrVectorDetail") throw new Error(`OdgShapeOrVectorDetailScreen rendered while the top of the stack is '${screen.kind}', not 'shapeOrVectorDetail' -- app.tsx's ScreenBody switch only mounts this component for that screen kind, so this cannot happen without a routing bug.`);
3020
3036
  return screen;
3021
3037
  }
3038
+ function vectorsParityMatch(liveVectors, contentVectors) {
3039
+ if (liveVectors.length !== contentVectors.length) return false;
3040
+ return liveVectors.every((live, index) => live.kind === contentVectors[index]?.kind);
3041
+ }
3022
3042
  function buildPageItems(doc, pageIndex) {
3023
3043
  const page = doc.editor.pages()[pageIndex];
3024
3044
  if (page === void 0) return [];
@@ -3026,9 +3046,12 @@ function buildPageItems(doc, pageIndex) {
3026
3046
  if (content.kind !== "drawing") throw new Error(`readOdgContent(doc.editor.toPackage()) returned a '${content.kind}' ContentDocument for an odg-format open document -- an odg package should always read back as the 'drawing' variant, so this indicates a real inconsistency in documents.js's own reader, not a state this screen should paper over.`);
3027
3047
  const contentPage = content.pages[pageIndex];
3028
3048
  if (contentPage === void 0) throw new Error(`readOdgContent found no page at index ${pageIndex}, but doc.editor.pages() has a page there -- the two read the same live package, so they should always agree on page count.`);
3029
- const vectorItems = contentPage.vectors.map((vector) => ({
3049
+ const liveVectors = page.vectors();
3050
+ const parityOk = vectorsParityMatch(liveVectors, contentPage.vectors);
3051
+ const vectorItems = contentPage.vectors.map((vector, index) => ({
3030
3052
  kind: "vector",
3031
- vector
3053
+ vector,
3054
+ liveVector: parityOk ? liveVectors[index] : void 0
3032
3055
  }));
3033
3056
  const shapeItems = page.shapes().map((shape, index) => {
3034
3057
  return {
@@ -3320,15 +3343,19 @@ function warnVectorIsViewOnly(dispatch, label) {
3320
3343
  dispatch({
3321
3344
  type: "SET_STATUS",
3322
3345
  severity: "info",
3323
- text: `${label} added -- documents.js exposes no way to enumerate an existing odg vector, so it will show in this list read-only and cannot be edited or removed from the TUI.`
3346
+ text: `${label} added -- but this page also has a vector element documents.js's OdgPage.vectors() cannot wrap, so every vector on it (including this one) shows read-only in this list rather than risk pairing the wrong live handle to the wrong row.`
3324
3347
  });
3325
3348
  }
3349
+ function warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, label) {
3350
+ const added = buildPageItems(doc, pageIndex).filter((item) => item.kind === "vector").at(-1);
3351
+ if (added !== void 0 && added.liveVector === void 0) warnVectorIsViewOnly(dispatch, label);
3352
+ }
3326
3353
  function inferImageFormat(path) {
3327
3354
  const extension = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
3328
3355
  if (extension === "png") return "png";
3329
3356
  if (extension === "jpg" || extension === "jpeg") return "jpeg";
3330
3357
  }
3331
- async function applyAddKind(kind, pageIndex, values, dispatch) {
3358
+ async function applyAddKind(kind, pageIndex, doc, values, dispatch) {
3332
3359
  switch (kind) {
3333
3360
  case "rect":
3334
3361
  dispatch({
@@ -3340,7 +3367,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
3340
3367
  stroke: parseStrokeField(requireFieldValue(values, "stroke"))
3341
3368
  }
3342
3369
  });
3343
- warnVectorIsViewOnly(dispatch, "Rectangle");
3370
+ warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Rectangle");
3344
3371
  return;
3345
3372
  case "ellipse":
3346
3373
  dispatch({
@@ -3352,7 +3379,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
3352
3379
  stroke: parseStrokeField(requireFieldValue(values, "stroke"))
3353
3380
  }
3354
3381
  });
3355
- warnVectorIsViewOnly(dispatch, "Ellipse");
3382
+ warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Ellipse");
3356
3383
  return;
3357
3384
  case "line":
3358
3385
  dispatch({
@@ -3377,7 +3404,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
3377
3404
  }
3378
3405
  }
3379
3406
  });
3380
- warnVectorIsViewOnly(dispatch, "Line");
3407
+ warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Line");
3381
3408
  return;
3382
3409
  case "path": {
3383
3410
  const frame = readFrame(values);
@@ -3391,7 +3418,7 @@ async function applyAddKind(kind, pageIndex, values, dispatch) {
3391
3418
  stroke: parseStrokeField(requireFieldValue(values, "stroke"))
3392
3419
  }
3393
3420
  });
3394
- warnVectorIsViewOnly(dispatch, "Path");
3421
+ warnIfVectorAddedReadOnly(doc, pageIndex, dispatch, "Path");
3395
3422
  return;
3396
3423
  }
3397
3424
  case "textbox":
@@ -3520,7 +3547,7 @@ function AddItemFlow(props) {
3520
3547
  fields: fieldsForAddKind(kind),
3521
3548
  onCancel: props.onCancel,
3522
3549
  onComplete: (values) => {
3523
- applyAddKind(kind, props.pageIndex, values, dispatch).then(props.onCreated);
3550
+ applyAddKind(kind, props.pageIndex, props.doc, values, dispatch).then(props.onCreated);
3524
3551
  }
3525
3552
  });
3526
3553
  }
@@ -3574,6 +3601,7 @@ function OdgPageDetailScreen() {
3574
3601
  });
3575
3602
  if (isAdding) return /* @__PURE__ */ jsx(AddItemFlow, {
3576
3603
  pageIndex,
3604
+ doc,
3577
3605
  isActive: !overlayOpen,
3578
3606
  onCancel: () => {
3579
3607
  setIsAdding(false);
@@ -3607,19 +3635,75 @@ function OdgPageDetailScreen() {
3607
3635
  }
3608
3636
  //#endregion
3609
3637
  //#region src/tui/screens/editors/odg/shape-or-vector-detail.tsx
3638
+ function buildVectorRows(vector, liveVector, dispatch) {
3639
+ const rows = [];
3640
+ if (vector.kind !== "line" && liveVector.kind !== "line") {
3641
+ const fillTarget = liveVector;
3642
+ rows.push({
3643
+ label: `Fill: ${vector.fill === void 0 ? "none" : formatColor$1(vector.fill)}`,
3644
+ currentValue: vector.fill === void 0 ? "" : `${vector.fill.r} ${vector.fill.g} ${vector.fill.b}`,
3645
+ commit: (raw) => {
3646
+ dispatch({
3647
+ type: "SET_VECTOR_FILL",
3648
+ vector: fillTarget,
3649
+ fill: parseColorField(raw)
3650
+ });
3651
+ }
3652
+ });
3653
+ }
3654
+ rows.push({
3655
+ label: `Stroke: ${vector.stroke === void 0 ? "none" : `${formatColor$1(vector.stroke.color)} ${formatPt(vector.stroke.widthPt)}pt`}`,
3656
+ currentValue: vector.stroke === void 0 ? "" : `${vector.stroke.color.r} ${vector.stroke.color.g} ${vector.stroke.color.b} ${vector.stroke.widthPt}`,
3657
+ commit: (raw) => {
3658
+ const stroke = parseStrokeField(raw);
3659
+ if (stroke === void 0) {
3660
+ dispatch({
3661
+ type: "SET_STATUS",
3662
+ severity: "warning",
3663
+ text: "A vector stroke cannot be cleared to none through this editor -- enter \"r g b widthPt\" (0-1 colour, pt width) instead"
3664
+ });
3665
+ return;
3666
+ }
3667
+ dispatch({
3668
+ type: "SET_VECTOR_STROKE",
3669
+ vector: liveVector,
3670
+ stroke
3671
+ });
3672
+ }
3673
+ });
3674
+ return rows;
3675
+ }
3610
3676
  function VectorDetail(props) {
3611
- return /* @__PURE__ */ jsxs(Box, {
3677
+ const dispatch = useAppDispatch();
3678
+ const [editingField, setEditingField] = useState(void 0);
3679
+ const [draft, setDraft] = useState("");
3680
+ const { vector, liveVector, isActive } = props;
3681
+ const rows = liveVector === void 0 ? [] : buildVectorRows(vector, liveVector, dispatch);
3682
+ const { selectedIndex } = useNavigationInput({
3683
+ itemCount: rows.length,
3684
+ isActive: isActive && editingField === void 0,
3685
+ onBack: () => {
3686
+ dispatch({ type: "POP_SCREEN" });
3687
+ },
3688
+ onSelect: (index) => {
3689
+ const row = rows[index];
3690
+ if (row === void 0) return;
3691
+ setDraft(row.currentValue);
3692
+ setEditingField(index);
3693
+ }
3694
+ });
3695
+ if (liveVector === void 0) return /* @__PURE__ */ jsxs(Box, {
3612
3696
  flexDirection: "column",
3613
3697
  children: [
3614
3698
  /* @__PURE__ */ jsxs(Text, {
3615
3699
  bold: true,
3616
- children: [vectorKindLabel(props.vector.kind), " (view-only)"]
3700
+ children: [vectorKindLabel(vector.kind), " (view-only)"]
3617
3701
  }),
3618
- /* @__PURE__ */ jsxs(Text, { children: ["Geometry: ", describeVectorGeometry(props.vector)] }),
3619
- /* @__PURE__ */ jsx(Text, { children: describeFillStroke(props.vector) }),
3702
+ /* @__PURE__ */ jsxs(Text, { children: ["Geometry: ", describeVectorGeometry(vector)] }),
3703
+ /* @__PURE__ */ jsx(Text, { children: describeFillStroke(vector) }),
3620
3704
  /* @__PURE__ */ jsx(Text, {
3621
3705
  color: "yellow",
3622
- children: "documents.js's OdgPage has no accessor for an existing rect/ellipse/line/path vector, so there is no live handle to edit or remove this one from the TUI -- open the file in a real ODF editor to change it."
3706
+ children: "This page's live vectors (documents.js's `OdgPage.vectors()`) don't line up one-to-one with what odf.js's own reader found here -- likely a `draw:circle`/`polygon`/`polyline`/`custom-shape` element the live accessor has no wrapper for, sitting alongside a plain rect/ellipse/line/path it does. Rather than risk pairing the wrong live handle to this row, every vector on this page is shown read-only until that mismatch is resolved outside this TUI. Open the file in a real ODF editor to change it."
3623
3707
  }),
3624
3708
  /* @__PURE__ */ jsx(Text, {
3625
3709
  dimColor: true,
@@ -3627,6 +3711,53 @@ function VectorDetail(props) {
3627
3711
  })
3628
3712
  ]
3629
3713
  });
3714
+ if (editingField !== void 0) {
3715
+ const row = rows[editingField];
3716
+ if (row === void 0) throw new Error(`VectorDetail is editing field index ${editingField}, but there are only ${rows.length} rows -- selecting a row always sets editingField to a valid index from that same rows array, so this indicates a bug in that selection.`);
3717
+ return /* @__PURE__ */ jsxs(Box, {
3718
+ flexDirection: "column",
3719
+ borderStyle: "round",
3720
+ paddingX: 1,
3721
+ children: [/* @__PURE__ */ jsx(Text, {
3722
+ bold: true,
3723
+ children: row.label
3724
+ }), /* @__PURE__ */ jsx(TextField, {
3725
+ value: draft,
3726
+ isFocused: true,
3727
+ onChange: setDraft,
3728
+ onCancel: () => {
3729
+ setEditingField(void 0);
3730
+ },
3731
+ onSubmit: (value) => {
3732
+ row.commit(value);
3733
+ setEditingField(void 0);
3734
+ }
3735
+ })]
3736
+ });
3737
+ }
3738
+ return /* @__PURE__ */ jsxs(Box, {
3739
+ flexDirection: "column",
3740
+ children: [
3741
+ /* @__PURE__ */ jsx(Text, {
3742
+ bold: true,
3743
+ children: vectorKindLabel(vector.kind)
3744
+ }),
3745
+ /* @__PURE__ */ jsxs(Text, { children: ["Geometry: ", describeVectorGeometry(vector)] }),
3746
+ /* @__PURE__ */ jsx(ListView, {
3747
+ items: rows,
3748
+ selectedIndex,
3749
+ reservedRows: 5,
3750
+ renderItem: (row, isSelected) => /* @__PURE__ */ jsxs(Text, {
3751
+ color: isSelected ? "cyan" : void 0,
3752
+ children: [isSelected ? "> " : " ", row.label]
3753
+ })
3754
+ }),
3755
+ /* @__PURE__ */ jsx(Text, {
3756
+ dimColor: true,
3757
+ children: "Enter to edit a field, Esc to go back"
3758
+ })
3759
+ ]
3760
+ });
3630
3761
  }
3631
3762
  function ShapeDetail(props) {
3632
3763
  const dispatch = useAppDispatch();
@@ -3796,7 +3927,7 @@ function OdgShapeOrVectorDetailScreen() {
3796
3927
  const item = items[itemIndex];
3797
3928
  useInput((input, key) => {
3798
3929
  if (key.escape || key.leftArrow || input === "h") dispatch({ type: "POP_SCREEN" });
3799
- }, { isActive: !overlayOpen && item?.kind !== "shape" });
3930
+ }, { isActive: !overlayOpen && item === void 0 });
3800
3931
  if (item === void 0) return /* @__PURE__ */ jsxs(Box, {
3801
3932
  flexDirection: "column",
3802
3933
  children: [/* @__PURE__ */ jsxs(Text, {
@@ -3813,7 +3944,11 @@ function OdgShapeOrVectorDetailScreen() {
3813
3944
  children: "Esc to go back"
3814
3945
  })]
3815
3946
  });
3816
- if (item.kind === "vector") return /* @__PURE__ */ jsx(VectorDetail, { vector: item.vector });
3947
+ if (item.kind === "vector") return /* @__PURE__ */ jsx(VectorDetail, {
3948
+ vector: item.vector,
3949
+ liveVector: item.liveVector,
3950
+ isActive: !overlayOpen
3951
+ });
3817
3952
  const vectorCount = items.filter((entry) => entry.kind === "vector").length;
3818
3953
  return /* @__PURE__ */ jsx(ShapeDetail, {
3819
3954
  pageIndex,
@@ -3955,10 +4090,6 @@ function formatRotationDeg(value) {
3955
4090
  return value === void 0 ? "(unset)" : `${Math.round(value * ROTATION_DISPLAY_PRECISION) / ROTATION_DISPLAY_PRECISION}°`;
3956
4091
  }
3957
4092
  function RotationField(props) {
3958
- if (!props.isEditable) return /* @__PURE__ */ jsx(Text, {
3959
- dimColor: true,
3960
- children: "[R] Rotation: not available for pptx shapes"
3961
- });
3962
4093
  if (props.isEditing) return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
3963
4094
  color: "cyan",
3964
4095
  children: "[R] Rotation (deg, blank to unset): "
@@ -3992,7 +4123,6 @@ const FIELD_LABELS = {
3992
4123
  width: "Width (pt)",
3993
4124
  height: "Height (pt)"
3994
4125
  };
3995
- const ROTATION_UNAVAILABLE_MESSAGE = "documents.js has no rotation setter for a pptx shape; rotate the shape in odp instead";
3996
4126
  const POINT_DISPLAY_PRECISION = 100;
3997
4127
  function formatPoints(value) {
3998
4128
  return value === void 0 ? "(unset)" : `${Math.round(value * POINT_DISPLAY_PRECISION) / POINT_DISPLAY_PRECISION}pt`;
@@ -4012,9 +4142,9 @@ function describeFieldValue(key, shape) {
4012
4142
  function roundForDisplay(value) {
4013
4143
  return Math.round(value * POINT_DISPLAY_PRECISION) / POINT_DISPLAY_PRECISION;
4014
4144
  }
4015
- function initialDraftFor(key, shape, rotatableShape) {
4145
+ function initialDraftFor(key, shape) {
4016
4146
  if (key === "rotation") {
4017
- const value = rotatableShape?.rotationDeg;
4147
+ const value = shape.rotationDeg;
4018
4148
  return value === void 0 ? "" : String(roundForDisplay(value));
4019
4149
  }
4020
4150
  if (key === "text") return shape.text;
@@ -4027,10 +4157,9 @@ function initialDraftFor(key, shape, rotatableShape) {
4027
4157
  }
4028
4158
  }
4029
4159
  function FieldRow(props) {
4030
- const { fieldKey, shape, rotatableShape, isOdp, isSelected, isEditing, draft, onDraftChange, onSubmit, onCancel } = props;
4160
+ const { fieldKey, shape, isSelected, isEditing, draft, onDraftChange, onSubmit, onCancel } = props;
4031
4161
  if (fieldKey === "rotation") return /* @__PURE__ */ jsx(RotationField, {
4032
- rotationDeg: rotatableShape?.rotationDeg,
4033
- isEditable: isOdp,
4162
+ rotationDeg: shape.rotationDeg,
4034
4163
  isSelected,
4035
4164
  isEditing,
4036
4165
  draftValue: draft,
@@ -4079,9 +4208,7 @@ function ShapeEditorScreen(props) {
4079
4208
  const overlayOpen = anyOverlayOpen(state);
4080
4209
  const doc = assertPresentationDocument(state.openDocument);
4081
4210
  const { slideIndex, shapeIndex } = props.screen;
4082
- const isOdp = doc.format === "odp";
4083
4211
  const shape = doc.editor.slides()[slideIndex]?.shapes()[shapeIndex];
4084
- const rotatableShape = doc.format === "odp" ? doc.editor.slides()[slideIndex]?.shapes()[shapeIndex] : void 0;
4085
4212
  const [editingField, setEditingField] = useState(void 0);
4086
4213
  const [draft, setDraft] = useState("");
4087
4214
  const { selectedIndex } = useNavigationInput({
@@ -4094,20 +4221,12 @@ function ShapeEditorScreen(props) {
4094
4221
  if (shape === void 0) return;
4095
4222
  const key = FIELD_KEYS[index];
4096
4223
  if (key === void 0) return;
4097
- if (key === "rotation" && !isOdp) {
4098
- dispatch({
4099
- type: "SET_STATUS",
4100
- severity: "warning",
4101
- text: ROTATION_UNAVAILABLE_MESSAGE
4102
- });
4103
- return;
4104
- }
4105
4224
  dispatch({
4106
4225
  type: "SET_SELECTION",
4107
4226
  key: selectionKeyFor(props.screen),
4108
4227
  index
4109
4228
  });
4110
- setDraft(initialDraftFor(key, shape, rotatableShape));
4229
+ setDraft(initialDraftFor(key, shape));
4111
4230
  setEditingField(key);
4112
4231
  }
4113
4232
  });
@@ -4232,8 +4351,6 @@ function ShapeEditorScreen(props) {
4232
4351
  renderItem: (key, isSelected) => /* @__PURE__ */ jsx(FieldRow, {
4233
4352
  fieldKey: key,
4234
4353
  shape,
4235
- rotatableShape,
4236
- isOdp,
4237
4354
  isSelected,
4238
4355
  isEditing: editingField === key,
4239
4356
  draft,
@@ -5339,28 +5456,40 @@ function OdsSpreadsheetGridScreen() {
5339
5456
  function ListEditorScreen() {
5340
5457
  const state = useAppState();
5341
5458
  const dispatch = useAppDispatch();
5459
+ const overlayOpen = anyOverlayOpen(state);
5342
5460
  const [isAdding, setIsAdding] = useState(false);
5343
5461
  const [newItemText, setNewItemText] = useState("");
5462
+ const [editingIndex, setEditingIndex] = useState(void 0);
5344
5463
  const screen = currentScreen(state);
5345
5464
  const doc = paragraphFamilyDocument(state.openDocument);
5346
5465
  const list = screen.kind === "listEditor" && doc?.format === "odt" ? doc.editor.lists()[screen.blockIndex] : void 0;
5347
- const itemCount = list === void 0 ? 0 : list.items().length;
5348
- useInput((input, key) => {
5349
- if (list === void 0 || screen.kind !== "listEditor") return;
5350
- if (key.escape) {
5466
+ const items = list === void 0 ? [] : list.items();
5467
+ const rows = items.map((item, index) => ({
5468
+ item,
5469
+ index
5470
+ }));
5471
+ const itemCount = items.length;
5472
+ const isNavigationActive = !overlayOpen && !isAdding && editingIndex === void 0;
5473
+ const { selectedIndex } = useNavigationInput({
5474
+ itemCount,
5475
+ isActive: isNavigationActive,
5476
+ onBack: () => {
5351
5477
  dispatch({ type: "POP_SCREEN" });
5352
- return;
5353
- }
5354
- if (input === "a") {
5478
+ },
5479
+ onSelect: (index) => {
5480
+ setEditingIndex(index);
5481
+ },
5482
+ onAppend: () => {
5355
5483
  setIsAdding(true);
5356
- return;
5357
5484
  }
5485
+ });
5486
+ useInput((input, key) => {
5358
5487
  if (key.tab || input === ">" || input === "<") dispatch({
5359
5488
  type: "SET_STATUS",
5360
5489
  severity: "warning",
5361
5490
  text: "Indenting a list item needs a new reducer action this pass didn't add -- OdtListItem.addNestedList() has no wiring yet"
5362
5491
  });
5363
- }, { isActive: !anyOverlayOpen(state) && !isAdding });
5492
+ }, { isActive: isNavigationActive });
5364
5493
  if (screen.kind !== "listEditor") return /* @__PURE__ */ jsx(Text, {
5365
5494
  color: "red",
5366
5495
  children: "ListEditorScreen rendered outside a listEditor screen."
@@ -5377,6 +5506,36 @@ function ListEditorScreen() {
5377
5506
  "."
5378
5507
  ]
5379
5508
  });
5509
+ if (editingIndex !== void 0) {
5510
+ const item = items[editingIndex];
5511
+ if (item === void 0) throw new Error(`ListEditorScreen is editing item index ${editingIndex}, but list ${screen.blockIndex} only has ${items.length} items -- selecting a row always sets editingIndex to a valid index from that same items array, so this indicates a bug in that selection.`);
5512
+ return /* @__PURE__ */ jsxs(Box, {
5513
+ flexDirection: "column",
5514
+ children: [/* @__PURE__ */ jsxs(Text, {
5515
+ bold: true,
5516
+ children: [
5517
+ "List ",
5518
+ screen.blockIndex,
5519
+ ", item ",
5520
+ editingIndex + 1
5521
+ ]
5522
+ }), /* @__PURE__ */ jsx(RunTextEditor, {
5523
+ initialText: item.text,
5524
+ onCommit: (text) => {
5525
+ dispatch({
5526
+ type: "SET_LIST_ITEM_TEXT",
5527
+ blockIndex: screen.blockIndex,
5528
+ itemIndex: editingIndex,
5529
+ text
5530
+ });
5531
+ setEditingIndex(void 0);
5532
+ },
5533
+ onCancel: () => {
5534
+ setEditingIndex(void 0);
5535
+ }
5536
+ })]
5537
+ });
5538
+ }
5380
5539
  return /* @__PURE__ */ jsxs(Box, {
5381
5540
  flexDirection: "column",
5382
5541
  children: [
@@ -5392,13 +5551,23 @@ function ListEditorScreen() {
5392
5551
  ")"
5393
5552
  ]
5394
5553
  }),
5395
- itemCount === 0 ? /* @__PURE__ */ jsx(Text, {
5396
- dimColor: true,
5397
- children: "This list has no items yet."
5398
- }) : Array.from({ length: itemCount }, (_, index) => /* @__PURE__ */ jsxs(Text, {
5399
- dimColor: true,
5400
- children: [index + 1, ". (item content is not readable through documents.js's OdtListItem API)"]
5401
- }, index)),
5554
+ /* @__PURE__ */ jsx(ListView, {
5555
+ items: rows,
5556
+ selectedIndex,
5557
+ emptyMessage: "This list has no items yet -- press 'a' to add one.",
5558
+ renderItem: (row, isSelected) => {
5559
+ const trimmed = row.item.text.trim();
5560
+ return /* @__PURE__ */ jsxs(Text, {
5561
+ color: isSelected ? "cyan" : void 0,
5562
+ inverse: isSelected,
5563
+ children: [
5564
+ row.index + 1,
5565
+ ". ",
5566
+ trimmed.length === 0 ? "(empty)" : row.item.text
5567
+ ]
5568
+ });
5569
+ }
5570
+ }),
5402
5571
  isAdding ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
5403
5572
  color: "cyan",
5404
5573
  children: "+ "
@@ -5422,7 +5591,7 @@ function ListEditorScreen() {
5422
5591
  }
5423
5592
  })] }) : /* @__PURE__ */ jsx(Text, {
5424
5593
  dimColor: true,
5425
- children: "a to add an item, Esc back"
5594
+ children: "Enter to edit an item, a to add, Esc back"
5426
5595
  })
5427
5596
  ]
5428
5597
  });
@@ -6441,6 +6610,10 @@ function AppShell({ startPath }) {
6441
6610
  dispatch({ type: "REQUEST_CLOSE" });
6442
6611
  return;
6443
6612
  }
6613
+ if (key.ctrl && input === "z") {
6614
+ dispatch({ type: "UNDO" });
6615
+ return;
6616
+ }
6444
6617
  if (input === ":") {
6445
6618
  dispatch({
6446
6619
  type: "OPEN_OVERLAY",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "CLI and interactive Ink TUI for documents.js: every docx/pptx/odt/odp/ods/odg/odf/pdf/odm/odb/xlsx/markdown conversion, bridge, and editor as a scriptable command or a terminal app.",
5
5
  "type": "module",
6
6
  "repository": {