@openpresentation/opf-editor 0.12.1 → 0.14.2
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/README.md +71 -60
- package/dist/annotations.d.ts +16 -16
- package/dist/annotations.js +71 -79
- package/dist/assets.d.ts +7 -7
- package/dist/assets.js +15 -15
- package/dist/background-options.d.ts +2 -2
- package/dist/background-options.js +6 -6
- package/dist/block-controls.js +7 -7
- package/dist/block-convert.d.ts +7 -7
- package/dist/block-convert.js +22 -21
- package/dist/blocks.d.ts +8 -8
- package/dist/blocks.js +41 -40
- package/dist/canvas-fields.js +35 -12
- package/dist/canvas.d.ts +22 -30
- package/dist/canvas.js +57 -64
- package/dist/chart-data.d.ts +10 -10
- package/dist/chart-data.js +21 -21
- package/dist/chart-options-panel.d.ts +2 -2
- package/dist/chart-options-panel.js +82 -8
- package/dist/chart-options.d.ts +35 -9
- package/dist/chart-options.js +133 -40
- package/dist/checks.js +21 -0
- package/dist/color-authoring.js +1 -1
- package/dist/content-actions.d.ts +15 -15
- package/dist/content-actions.js +71 -70
- package/dist/content-controls.js +12 -12
- package/dist/data-grid.js +7 -8
- package/dist/data.d.ts +4 -4
- package/dist/data.js +5 -5
- package/dist/dataset-refs.d.ts +2 -2
- package/dist/dataset-refs.js +5 -5
- package/dist/design-controls.js +61 -44
- package/dist/design-options.d.ts +16 -14
- package/dist/design-options.js +86 -74
- package/dist/edit-helpers.js +8 -7
- package/dist/exact-source.js +5 -5
- package/dist/export.d.ts +5 -7
- package/dist/export.js +40 -14
- package/dist/find-panel.js +5 -5
- package/dist/find-replace.d.ts +3 -3
- package/dist/find-replace.js +23 -20
- package/dist/font-gate.js +49 -58
- package/dist/galleries.js +36 -52
- package/dist/grid-model.d.ts +25 -27
- package/dist/grid-model.js +92 -102
- package/dist/image-crop.d.ts +4 -4
- package/dist/image-crop.js +20 -20
- package/dist/image-cropper.js +3 -3
- package/dist/index.d.ts +28 -28
- package/dist/index.js +74 -90
- package/dist/json-options.js +17 -16
- package/dist/layout-handles.js +5 -5
- package/dist/layout-placeholders.js +4 -4
- package/dist/layout.d.ts +2 -2
- package/dist/layout.js +8 -7
- package/dist/numbering-panel.js +3 -5
- package/dist/numbering.d.ts +3 -5
- package/dist/numbering.js +14 -26
- package/dist/outline-view.js +24 -24
- package/dist/outline.d.ts +12 -12
- package/dist/outline.js +52 -50
- package/dist/persistence.d.ts +1 -1
- package/dist/persistence.js +19 -18
- package/dist/presentation-ids.js +2 -2
- package/dist/react.js +3 -3
- package/dist/review-panel.d.ts +30 -13
- package/dist/review-panel.js +154 -53
- package/dist/review.d.ts +36 -43
- package/dist/review.js +92 -53
- package/dist/rich-text-input.js +4 -3
- package/dist/rich-text-toolbar.js +5 -3
- package/dist/rich-text.js +5 -2
- package/dist/schema-inspector.d.ts +2 -2
- package/dist/schema-inspector.js +16 -11
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +25 -5
- package/dist/slide-manager.d.ts +2 -2
- package/dist/slide-manager.js +18 -18
- package/dist/slides.d.ts +20 -20
- package/dist/slides.js +99 -97
- package/dist/svelte.js +1 -1
- package/dist/switches.d.ts +10 -10
- package/dist/switches.js +124 -114
- package/dist/table-options.d.ts +8 -8
- package/dist/table-options.js +28 -27
- package/dist/table-structure.d.ts +10 -10
- package/dist/table-structure.js +21 -21
- package/dist/template-panel.d.ts +3 -3
- package/dist/template-panel.js +37 -13
- package/dist/templates.d.ts +22 -11
- package/dist/templates.js +48 -55
- package/dist/transfer.d.ts +4 -4
- package/dist/transfer.js +49 -59
- package/package.json +8 -6
- package/dist/font-defaults.js +0 -25
package/dist/canvas.js
CHANGED
|
@@ -2,16 +2,17 @@ import {
|
|
|
2
2
|
createEditorSession,
|
|
3
3
|
opfPathToJsonPointer,
|
|
4
4
|
applyJsonPatch,
|
|
5
|
-
validateOpfDocument,
|
|
6
5
|
getValueAtPath,
|
|
7
6
|
splitOpfPath,
|
|
8
7
|
} from "./index.js";
|
|
9
8
|
import {
|
|
10
|
-
|
|
9
|
+
renderSlideSvg,
|
|
11
10
|
resolvePresentation,
|
|
12
11
|
} from "@openpresentation/opf-render/svg";
|
|
13
12
|
import {
|
|
14
13
|
getEditableFields,
|
|
14
|
+
emptyLike,
|
|
15
|
+
fieldChoices,
|
|
15
16
|
parseCanvasValue,
|
|
16
17
|
createCanvasDraft,
|
|
17
18
|
} from "./canvas-fields.js";
|
|
@@ -21,9 +22,10 @@ import { createRichTextInput } from "./rich-text-input.js";
|
|
|
21
22
|
import { textInputOffsetAtPoint } from "./text-pointer.js";
|
|
22
23
|
import { createRichTextToolbar } from "./rich-text-toolbar.js";
|
|
23
24
|
import { createImageCropper } from "./image-cropper.js";
|
|
24
|
-
import { FONTS_PENDING, fontsPendingError, whenFontsReady } from "./font-gate.js";
|
|
25
|
+
import { FONTS_PENDING, fontGate, fontsPendingError, previewFonts, whenFontsReady } from "./font-gate.js";
|
|
26
|
+
import { checkFormat, firstErrorMessage } from "./checks.js";
|
|
25
27
|
export { getEditableFields } from "./canvas-fields.js";
|
|
26
|
-
export {
|
|
28
|
+
export { whenFontsReady, FONTS_PENDING, FONTS_UNAVAILABLE } from "./font-gate.js";
|
|
27
29
|
|
|
28
30
|
/** Allocated placeholder or internal-part bounds for the selection outline, not glyph ink. */
|
|
29
31
|
export function allocatedSelectionBox(node, item) {
|
|
@@ -65,10 +67,12 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
65
67
|
win = doc.defaultView;
|
|
66
68
|
const editor =
|
|
67
69
|
options.editor ??
|
|
68
|
-
createEditorSession(options.
|
|
69
|
-
// `options.fonts` is
|
|
70
|
-
//
|
|
71
|
-
|
|
70
|
+
createEditorSession(options.presentation, { rejectInvalid: true });
|
|
71
|
+
// `options.fonts` is the renderer's fonts handle (`loadFonts()`): it measures the text and draws the faces. A document whose faces
|
|
72
|
+
// are not loaded yet is never rendered: the canvas has the handle load them first and shows "Loading fonts…" meanwhile. Without a
|
|
73
|
+
// handle every document renders at once, with core's portable text estimate.
|
|
74
|
+
const handle = options.fonts, fonts = fontGate(handle);
|
|
75
|
+
const drawOptions = (extra) => ({ ...renderOptions, fonts: previewFonts(handle), ...extra });
|
|
72
76
|
let slideIndex = options.slideIndex ?? 0,
|
|
73
77
|
// RR-32: the canvas edits the document as authored, so a template's {{tokens}} stay visible and an inline edit never
|
|
74
78
|
// overwrites one with its resolved text. The Fill template panel previews the resolved deck. Pass `variables` to override.
|
|
@@ -120,7 +124,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
120
124
|
onTyping: beginEdit,
|
|
121
125
|
onProperties(path) { if (commit()) openProperties(path, editor.get(path)); },
|
|
122
126
|
validateChange(path, value) {
|
|
123
|
-
validateRender(createCanvasDraft(editor.
|
|
127
|
+
validateRender(createCanvasDraft(editor.presentation, path, value));
|
|
124
128
|
},
|
|
125
129
|
onChange(path) { clearNotice(); options.onCommit?.({path, editor}); }
|
|
126
130
|
});
|
|
@@ -144,7 +148,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
144
148
|
const blockControls = createBlockControls(root, {
|
|
145
149
|
editor, render: renderFor, beforeEdit: () => {if(!commit())return false;richToolbar.hide();return true;}, enabled: () => layoutHandles.enabled,
|
|
146
150
|
isEditing: () => !!active || !!layoutHandles.editingPath, slideIndex: () => slideIndex,
|
|
147
|
-
validate:
|
|
151
|
+
validate: presentation => validateRender(presentation),
|
|
148
152
|
clearError: clearNotice, onError: report,
|
|
149
153
|
onMove: path => choose(splitOpfPath(path).join(".")),
|
|
150
154
|
onCommit: value => options.onCommit?.(value),
|
|
@@ -175,20 +179,20 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
175
179
|
const pendingMessage = "Loading fonts for this document…";
|
|
176
180
|
// A synchronous render or validation of a document whose faces are still loading fails with a clear "fonts-pending"
|
|
177
181
|
// error and starts the load, so the next attempt succeeds. It never draws glyphs the registry cannot provide.
|
|
178
|
-
function requireFonts(
|
|
179
|
-
const pending = fonts?.pending(
|
|
182
|
+
function requireFonts(presentation) {
|
|
183
|
+
const pending = fonts?.pending(presentation, renderOptions) ?? [];
|
|
180
184
|
if (!pending.length) return;
|
|
181
185
|
notice.hidden = false;
|
|
182
186
|
notice.textContent = pendingMessage;
|
|
183
|
-
fonts.ensure(
|
|
187
|
+
fonts.ensure(presentation, { renderOptions }).then(
|
|
184
188
|
() => { if (!disposed && notice.textContent === pendingMessage) clearNotice(); },
|
|
185
189
|
(error) => { if (!disposed) report(error); },
|
|
186
190
|
);
|
|
187
191
|
throw fontsPendingError(pending);
|
|
188
192
|
}
|
|
189
|
-
function validateRender(
|
|
190
|
-
requireFonts(
|
|
191
|
-
return
|
|
193
|
+
function validateRender(presentation) {
|
|
194
|
+
requireFonts(presentation);
|
|
195
|
+
return renderSlideSvg(presentation, slideIndex, drawOptions());
|
|
192
196
|
}
|
|
193
197
|
function fontsState(kind, detail) {
|
|
194
198
|
fontsShown = true;
|
|
@@ -218,7 +222,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
218
222
|
function show(target) {
|
|
219
223
|
if (disposed) return;
|
|
220
224
|
const token = ++showToken;
|
|
221
|
-
return whenFontsReady(fonts, target ?? editor.
|
|
225
|
+
return whenFontsReady(fonts, target ?? editor.presentation, {
|
|
222
226
|
renderOptions,
|
|
223
227
|
isCurrent: () => !disposed && token === showToken,
|
|
224
228
|
loading: (pending) => fontsState("loading", pending),
|
|
@@ -234,27 +238,23 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
234
238
|
});
|
|
235
239
|
}
|
|
236
240
|
// Renders now when the document's fonts are loaded (errors throw to the caller), otherwise after loading them.
|
|
237
|
-
function renderFor(
|
|
238
|
-
if (fonts?.pending(
|
|
239
|
-
else render(
|
|
241
|
+
function renderFor(presentation) {
|
|
242
|
+
if (fonts?.pending(presentation ?? editor.presentation, renderOptions).length) show(presentation);
|
|
243
|
+
else render(presentation);
|
|
240
244
|
}
|
|
241
|
-
function render(
|
|
245
|
+
function render(presentation = editor.presentation) {
|
|
242
246
|
if (disposed) return;
|
|
243
|
-
requireFonts(
|
|
247
|
+
requireFonts(presentation);
|
|
244
248
|
showToken++;
|
|
245
|
-
const slides =
|
|
249
|
+
const slides = presentation.slides ?? [];
|
|
246
250
|
slideIndex = Math.max(0, Math.min(slideIndex, slides.length - 1));
|
|
247
|
-
const svgText =
|
|
248
|
-
...renderOptions,
|
|
249
|
-
slideIndex,
|
|
250
|
-
trace: true,
|
|
251
|
-
});
|
|
251
|
+
const svgText = renderSlideSvg(presentation, slideIndex, drawOptions({ trace: true }));
|
|
252
252
|
preview.innerHTML = svgText;
|
|
253
253
|
const svg = preview.querySelector("svg");
|
|
254
254
|
svg.setAttribute("role", "group");
|
|
255
255
|
svg.setAttribute("aria-label", "Editable slide");
|
|
256
256
|
svg.removeAttribute("aria-labelledby");
|
|
257
|
-
const geometry = resolvePresentation(
|
|
257
|
+
const geometry = resolvePresentation(presentation, drawOptions()).slides[
|
|
258
258
|
slideIndex
|
|
259
259
|
].geometry;
|
|
260
260
|
const candidates = [...svg.querySelectorAll("[data-opf-path]")];
|
|
@@ -262,7 +262,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
262
262
|
for (const node of candidates) {
|
|
263
263
|
const path = node.getAttribute("data-opf-path");
|
|
264
264
|
const item = geometry.items.find((item) => item.path === path);
|
|
265
|
-
const value = getValueAtPath(
|
|
265
|
+
const value = getValueAtPath(presentation, path);
|
|
266
266
|
if (
|
|
267
267
|
seen.has(path) ||
|
|
268
268
|
node.getAttribute('data-opf-generated') === 'true' ||
|
|
@@ -340,8 +340,8 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
340
340
|
});
|
|
341
341
|
if (path === selectedPath) node.setAttribute("data-canvas-selected", "");
|
|
342
342
|
}
|
|
343
|
-
layoutHandles.update(
|
|
344
|
-
blockControls.update(
|
|
343
|
+
layoutHandles.update(presentation, geometry);
|
|
344
|
+
blockControls.update(presentation, geometry);
|
|
345
345
|
imageCropper?.update();
|
|
346
346
|
if (active?.kind === "text") positionInput();
|
|
347
347
|
if (active?.kind === "rich-text") active.rich?.update();
|
|
@@ -350,7 +350,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
350
350
|
options.onFonts?.({ state: "ready" });
|
|
351
351
|
}
|
|
352
352
|
options.onRender?.({
|
|
353
|
-
|
|
353
|
+
presentation,
|
|
354
354
|
slideIndex,
|
|
355
355
|
svg,
|
|
356
356
|
geometry,
|
|
@@ -392,7 +392,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
392
392
|
clearNotice();
|
|
393
393
|
render(draft);
|
|
394
394
|
options.onDraft?.({
|
|
395
|
-
|
|
395
|
+
presentation: draft,
|
|
396
396
|
path: active.path,
|
|
397
397
|
value: undefined,
|
|
398
398
|
});
|
|
@@ -403,13 +403,13 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
403
403
|
}
|
|
404
404
|
try {
|
|
405
405
|
const value = active.kind === "rich-text" ? active.rich.value : parseCanvasValue(active.input.value, active.type, active.originalValue);
|
|
406
|
-
const draft = createCanvasDraft(editor.
|
|
406
|
+
const draft = createCanvasDraft(editor.presentation, active.path, value);
|
|
407
407
|
if (deferDraft(draft)) return;
|
|
408
408
|
clearNotice();
|
|
409
409
|
active.valid = true;
|
|
410
410
|
active.draft = draft;
|
|
411
411
|
render(draft);
|
|
412
|
-
options.onDraft?.({
|
|
412
|
+
options.onDraft?.({ presentation: draft, path: active.path, value });
|
|
413
413
|
} catch (error) {
|
|
414
414
|
active.valid = false;
|
|
415
415
|
report(error);
|
|
@@ -429,8 +429,8 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
429
429
|
const font = win.getComputedStyle(text),
|
|
430
430
|
fontSize = parseFloat(font.fontSize),
|
|
431
431
|
geometry = resolvePresentation(
|
|
432
|
-
active.draft ?? editor.
|
|
433
|
-
|
|
432
|
+
active.draft ?? editor.presentation,
|
|
433
|
+
drawOptions(),
|
|
434
434
|
).slides[slideIndex].geometry;
|
|
435
435
|
const item = geometry.items.find((item) => item.path === active.path);
|
|
436
436
|
const metricLayout=geometry.items.find(item=>item.metricLayout?.parts.some(part=>part.path===active.path))?.metricLayout;
|
|
@@ -600,7 +600,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
600
600
|
// Only offer rich text where the canonical schema accepts TextRun[].
|
|
601
601
|
if (typeof value === "string") {
|
|
602
602
|
try {
|
|
603
|
-
createCanvasDraft(editor.
|
|
603
|
+
createCanvasDraft(editor.presentation, path, [value]);
|
|
604
604
|
const format = doc.createElement("button");
|
|
605
605
|
format.type = "button";
|
|
606
606
|
format.textContent = "Format text";
|
|
@@ -683,11 +683,11 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
683
683
|
}));
|
|
684
684
|
}
|
|
685
685
|
function propertyDraft(edit) {
|
|
686
|
-
const draft = applyJsonPatch(editor.
|
|
687
|
-
validation =
|
|
686
|
+
const draft = applyJsonPatch(editor.presentation, propertyPatches(edit)),
|
|
687
|
+
validation = checkFormat(draft);
|
|
688
688
|
if (!validation.valid)
|
|
689
689
|
throw new Error(
|
|
690
|
-
validation
|
|
690
|
+
firstErrorMessage(validation, "This change is not valid OPF."),
|
|
691
691
|
);
|
|
692
692
|
return draft;
|
|
693
693
|
}
|
|
@@ -706,10 +706,20 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
706
706
|
label.style.cssText =
|
|
707
707
|
"display:block;margin-top:10px;color:#756b85;font-size:11px";
|
|
708
708
|
label.textContent = field.label;
|
|
709
|
+
const choices = field.type === "string" ? fieldChoices(field) : undefined;
|
|
709
710
|
const input = doc.createElement(
|
|
710
|
-
field.type === "boolean" ? "input" : "textarea",
|
|
711
|
+
choices ? "select" : field.type === "boolean" ? "input" : "textarea",
|
|
711
712
|
);
|
|
712
|
-
if (
|
|
713
|
+
if (choices) {
|
|
714
|
+
// A fixed set of values (a timeline event's status) is a menu, not free text.
|
|
715
|
+
for (const choice of choices) {
|
|
716
|
+
const option = doc.createElement("option");
|
|
717
|
+
option.value = choice;
|
|
718
|
+
option.textContent = choice;
|
|
719
|
+
input.append(option);
|
|
720
|
+
}
|
|
721
|
+
input.value = field.value;
|
|
722
|
+
} else if (field.type === "boolean") {
|
|
713
723
|
input.type = "checkbox";
|
|
714
724
|
input.checked = field.value;
|
|
715
725
|
} else {
|
|
@@ -780,26 +790,9 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
780
790
|
if (!commit()) return;
|
|
781
791
|
try {
|
|
782
792
|
const values = editor.get(array.path);
|
|
783
|
-
const empty = (value, key = "") =>
|
|
784
|
-
Array.isArray(value)
|
|
785
|
-
? value.map((child) => empty(child))
|
|
786
|
-
: value && typeof value === "object"
|
|
787
|
-
? Object.fromEntries(
|
|
788
|
-
Object.entries(value).map(([key, child]) => [
|
|
789
|
-
key,
|
|
790
|
-
empty(child, key),
|
|
791
|
-
]),
|
|
792
|
-
)
|
|
793
|
-
: typeof value === "number"
|
|
794
|
-
? 0
|
|
795
|
-
: typeof value === "boolean"
|
|
796
|
-
? false
|
|
797
|
-
: ["type", "mode", "language"].includes(key)
|
|
798
|
-
? value
|
|
799
|
-
: "";
|
|
800
793
|
const next = remove
|
|
801
794
|
? values.slice(0, -1)
|
|
802
|
-
: [...values,
|
|
795
|
+
: [...values, emptyLike(values.at(-1) ?? "")];
|
|
803
796
|
editor.set(array.path, next, {
|
|
804
797
|
source: "canvas-collection",
|
|
805
798
|
rejectInvalid: true,
|
|
@@ -876,7 +869,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
876
869
|
value = parseCanvasValue(edit.input.value, edit.type, edit.originalValue);
|
|
877
870
|
else value = propertyDraft(edit);
|
|
878
871
|
if (edit.kind === "text" || edit.kind === "rich-text")
|
|
879
|
-
validateRender(createCanvasDraft(editor.
|
|
872
|
+
validateRender(createCanvasDraft(editor.presentation, edit.path, value));
|
|
880
873
|
committing = true;
|
|
881
874
|
if ((edit.kind === "text" || edit.kind === "rich-text") && JSON.stringify(value) !== edit.base)
|
|
882
875
|
editor.set(edit.path, value, { source: "canvas", rejectInvalid: true });
|
|
@@ -928,7 +921,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
928
921
|
options.onCancel?.({ path });
|
|
929
922
|
}
|
|
930
923
|
function setSlide(index) {
|
|
931
|
-
if (!Number.isInteger(index) || !editor.
|
|
924
|
+
if (!Number.isInteger(index) || !editor.presentation.slides?.[index])
|
|
932
925
|
throw new RangeError("Slide index is out of range.");
|
|
933
926
|
if (index === slideIndex) {
|
|
934
927
|
if (!active) renderFor();
|
package/dist/chart-data.d.ts
CHANGED
|
@@ -2,29 +2,29 @@ import type { EditorSession } from "./index.js";
|
|
|
2
2
|
import type { GridAddress, GridCellEdit, GridChange, GridOptions, GridSortOptions, PreparedGridChange } from "./grid-model.js";
|
|
3
3
|
|
|
4
4
|
/** Set chart cells from text or typed values: names and categories are text, series cells are numbers read in the number format (blank is a gap, never 0). One patch; every problem is in `error.issues`. */
|
|
5
|
-
export declare function prepareChartCells(
|
|
5
|
+
export declare function prepareChartCells(presentation: unknown, chartPath: string, edits: GridCellEdit[], options?: GridOptions): PreparedGridChange;
|
|
6
6
|
export declare function setChartCells(editor: EditorSession, chartPath: string, edits: GridCellEdit[], options?: GridOptions): GridChange;
|
|
7
|
-
export declare function prepareChartPaste(
|
|
7
|
+
export declare function prepareChartPaste(presentation: unknown, chartPath: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): PreparedGridChange;
|
|
8
8
|
export declare function pasteChartText(editor: EditorSession, chartPath: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): GridChange;
|
|
9
9
|
/** Insert `count` empty rows (categories) so the first is row `at`. */
|
|
10
|
-
export declare function prepareChartInsertRows(
|
|
10
|
+
export declare function prepareChartInsertRows(presentation: unknown, chartPath: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
|
|
11
11
|
export declare function insertChartRows(editor: EditorSession, chartPath: string, at: number, count?: number, options?: GridOptions): GridChange;
|
|
12
|
-
export declare function prepareChartDeleteRows(
|
|
12
|
+
export declare function prepareChartDeleteRows(presentation: unknown, chartPath: string, indices: number[], options?: GridOptions): PreparedGridChange;
|
|
13
13
|
export declare function deleteChartRows(editor: EditorSession, chartPath: string, indices: number[], options?: GridOptions): GridChange;
|
|
14
|
-
export declare function prepareChartMoveRows(
|
|
14
|
+
export declare function prepareChartMoveRows(presentation: unknown, chartPath: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
|
|
15
15
|
export declare function moveChartRows(editor: EditorSession, chartPath: string, from: number, to: number, count?: number, options?: GridOptions): GridChange;
|
|
16
16
|
/** Insert `count` unnamed series so the first is column `at`. */
|
|
17
|
-
export declare function prepareChartInsertColumns(
|
|
17
|
+
export declare function prepareChartInsertColumns(presentation: unknown, chartPath: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
|
|
18
18
|
export declare function insertChartColumns(editor: EditorSession, chartPath: string, at: number, count?: number, options?: GridOptions): GridChange;
|
|
19
|
-
export declare function prepareChartDeleteColumns(
|
|
19
|
+
export declare function prepareChartDeleteColumns(presentation: unknown, chartPath: string, indices: number[], options?: GridOptions): PreparedGridChange;
|
|
20
20
|
export declare function deleteChartColumns(editor: EditorSession, chartPath: string, indices: number[], options?: GridOptions): GridChange;
|
|
21
|
-
export declare function prepareChartMoveColumns(
|
|
21
|
+
export declare function prepareChartMoveColumns(presentation: unknown, chartPath: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
|
|
22
22
|
export declare function moveChartColumns(editor: EditorSession, chartPath: string, from: number, to: number, count?: number, options?: GridOptions): GridChange;
|
|
23
23
|
/** Sort the rows (categories) by a column, stably and by type. */
|
|
24
|
-
export declare function prepareChartSort(
|
|
24
|
+
export declare function prepareChartSort(presentation: unknown, chartPath: string, column: number, options?: GridSortOptions): PreparedGridChange;
|
|
25
25
|
export declare function sortChartRows(editor: EditorSession, chartPath: string, column: number, options?: GridSortOptions): GridChange;
|
|
26
26
|
/** Swap categories and series: the first column's values become the series names and each series becomes a row. Numeric categories become text. */
|
|
27
|
-
export declare function prepareChartTranspose(
|
|
27
|
+
export declare function prepareChartTranspose(presentation: unknown, chartPath: string): PreparedGridChange;
|
|
28
28
|
export declare function transposeChart(editor: EditorSession, chartPath: string, options?: { meta?: Record<string, unknown> }): GridChange;
|
|
29
29
|
/** Rename a series: the label of data column `column` (1 or more). */
|
|
30
30
|
export declare function renameChartSeries(editor: EditorSession, chartPath: string, column: number, name: string, options?: { meta?: Record<string, unknown> }): GridChange;
|
package/dist/chart-data.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// second). Cells are text for names and categories and numbers for values; a gap is null and stays a gap, never 0. Each operation is
|
|
4
4
|
// one validated, undoable patch (grid-model.js has the implementation; grid-text.js documents number reading).
|
|
5
5
|
//
|
|
6
|
-
// `prepare*` computes `{
|
|
6
|
+
// `prepare*` computes `{ presentation, patches, changed }` without touching a session; the session forms apply it as one undo step.
|
|
7
7
|
import {
|
|
8
8
|
prepareGridCells,
|
|
9
9
|
preparePaste,
|
|
@@ -26,64 +26,64 @@ import {
|
|
|
26
26
|
prepareTranspose,
|
|
27
27
|
transposeGridData,
|
|
28
28
|
} from "./grid-model.js";
|
|
29
|
-
export function prepareChartCells(
|
|
30
|
-
return prepareGridCells(
|
|
29
|
+
export function prepareChartCells(presentation, chartPath, edits, options = {}) {
|
|
30
|
+
return prepareGridCells(presentation, chartPath, edits, { ...options, kind: "chart" });
|
|
31
31
|
}
|
|
32
32
|
export function setChartCells(editor, chartPath, edits, options = {}) {
|
|
33
33
|
return setGridCells(editor, chartPath, edits, { ...options, kind: "chart" });
|
|
34
34
|
}
|
|
35
|
-
export function prepareChartPaste(
|
|
36
|
-
return preparePaste(
|
|
35
|
+
export function prepareChartPaste(presentation, chartPath, anchor, source, options = {}) {
|
|
36
|
+
return preparePaste(presentation, chartPath, anchor, source, { ...options, kind: "chart" });
|
|
37
37
|
}
|
|
38
38
|
export function pasteChartText(editor, chartPath, anchor, source, options = {}) {
|
|
39
39
|
return pasteGridText(editor, chartPath, anchor, source, { ...options, kind: "chart" });
|
|
40
40
|
}
|
|
41
|
-
export function prepareChartInsertRows(
|
|
42
|
-
return prepareInsertRows(
|
|
41
|
+
export function prepareChartInsertRows(presentation, chartPath, at, count = 1, options = {}) {
|
|
42
|
+
return prepareInsertRows(presentation, chartPath, at, count, { ...options, kind: "chart" });
|
|
43
43
|
}
|
|
44
44
|
export function insertChartRows(editor, chartPath, at, count = 1, options = {}) {
|
|
45
45
|
return insertGridRows(editor, chartPath, at, count, { ...options, kind: "chart" });
|
|
46
46
|
}
|
|
47
|
-
export function prepareChartDeleteRows(
|
|
48
|
-
return prepareDeleteRows(
|
|
47
|
+
export function prepareChartDeleteRows(presentation, chartPath, indices, options = {}) {
|
|
48
|
+
return prepareDeleteRows(presentation, chartPath, indices, { ...options, kind: "chart" });
|
|
49
49
|
}
|
|
50
50
|
export function deleteChartRows(editor, chartPath, indices, options = {}) {
|
|
51
51
|
return deleteGridRows(editor, chartPath, indices, { ...options, kind: "chart" });
|
|
52
52
|
}
|
|
53
|
-
export function prepareChartMoveRows(
|
|
54
|
-
return prepareMoveRows(
|
|
53
|
+
export function prepareChartMoveRows(presentation, chartPath, from, to, count = 1, options = {}) {
|
|
54
|
+
return prepareMoveRows(presentation, chartPath, from, to, count, { ...options, kind: "chart" });
|
|
55
55
|
}
|
|
56
56
|
export function moveChartRows(editor, chartPath, from, to, count = 1, options = {}) {
|
|
57
57
|
return moveGridRows(editor, chartPath, from, to, count, { ...options, kind: "chart" });
|
|
58
58
|
}
|
|
59
|
-
export function prepareChartInsertColumns(
|
|
60
|
-
return prepareInsertColumns(
|
|
59
|
+
export function prepareChartInsertColumns(presentation, chartPath, at, count = 1, options = {}) {
|
|
60
|
+
return prepareInsertColumns(presentation, chartPath, at, count, { ...options, kind: "chart" });
|
|
61
61
|
}
|
|
62
62
|
export function insertChartColumns(editor, chartPath, at, count = 1, options = {}) {
|
|
63
63
|
return insertGridColumns(editor, chartPath, at, count, { ...options, kind: "chart" });
|
|
64
64
|
}
|
|
65
|
-
export function prepareChartDeleteColumns(
|
|
66
|
-
return prepareDeleteColumns(
|
|
65
|
+
export function prepareChartDeleteColumns(presentation, chartPath, indices, options = {}) {
|
|
66
|
+
return prepareDeleteColumns(presentation, chartPath, indices, { ...options, kind: "chart" });
|
|
67
67
|
}
|
|
68
68
|
export function deleteChartColumns(editor, chartPath, indices, options = {}) {
|
|
69
69
|
return deleteGridColumns(editor, chartPath, indices, { ...options, kind: "chart" });
|
|
70
70
|
}
|
|
71
|
-
export function prepareChartMoveColumns(
|
|
72
|
-
return prepareMoveColumns(
|
|
71
|
+
export function prepareChartMoveColumns(presentation, chartPath, from, to, count = 1, options = {}) {
|
|
72
|
+
return prepareMoveColumns(presentation, chartPath, from, to, count, { ...options, kind: "chart" });
|
|
73
73
|
}
|
|
74
74
|
export function moveChartColumns(editor, chartPath, from, to, count = 1, options = {}) {
|
|
75
75
|
return moveGridColumns(editor, chartPath, from, to, count, { ...options, kind: "chart" });
|
|
76
76
|
}
|
|
77
|
-
export function prepareChartSort(
|
|
78
|
-
return prepareSortRows(
|
|
77
|
+
export function prepareChartSort(presentation, chartPath, column, options = {}) {
|
|
78
|
+
return prepareSortRows(presentation, chartPath, column, { ...options, kind: "chart" });
|
|
79
79
|
}
|
|
80
80
|
export function sortChartRows(editor, chartPath, column, options = {}) {
|
|
81
81
|
return sortGridRows(editor, chartPath, column, { ...options, kind: "chart" });
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/** Swap categories and series: the first column's values become the series names and each series becomes a row. See {@link prepareTranspose}. */
|
|
85
|
-
export function prepareChartTranspose(
|
|
86
|
-
return prepareTranspose(
|
|
85
|
+
export function prepareChartTranspose(presentation, chartPath) {
|
|
86
|
+
return prepareTranspose(presentation, chartPath);
|
|
87
87
|
}
|
|
88
88
|
export function transposeChart(editor, chartPath, options = {}) {
|
|
89
89
|
return transposeGridData(editor, chartPath, options);
|
|
@@ -7,8 +7,8 @@ export interface ChartOptionsPanelOptions {
|
|
|
7
7
|
onStatus?: (message: string) => void;
|
|
8
8
|
}
|
|
9
9
|
export interface ChartOptionsPanel {
|
|
10
|
-
/** The panel's root element
|
|
11
|
-
element: HTMLElement
|
|
10
|
+
/** The panel's root element (hidden while no chart is selected). */
|
|
11
|
+
element: HTMLElement;
|
|
12
12
|
/** Re-read the selection and the chart; the panel also refreshes on every editor change. */
|
|
13
13
|
refresh(): void;
|
|
14
14
|
destroy(): void;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
// The chart options panel (RR-35): axis titles, legend position and data labels for the selected chart
|
|
1
|
+
// The chart options panel (RR-35): axis titles, legend position and data labels for the selected chart, plus (FA-09) its alt text and
|
|
2
|
+
// (FA-14) which series and categories to highlight.
|
|
2
3
|
// It mounts into any host element over an editor session; every control commits one undoable change through
|
|
3
4
|
// `setChartOptions` (src/chart-options.js), the canvas redraws, and Undo restores the chart. The panel shows only what
|
|
4
|
-
// the selected chart type can show (core's support table), and hides itself when no chart is selected.
|
|
5
|
-
|
|
5
|
+
// the selected chart type can show (core's support table), and hides itself when no chart is selected. On a combo chart
|
|
6
|
+
// (FA-15) a "Series" group sets which series are lines and which lines use the secondary value axis, and a secondary axis
|
|
7
|
+
// title field appears while a line uses that axis.
|
|
8
|
+
import { CHART_LABEL_CONTENT, CHART_LEGEND_POSITIONS, parseChartPath, readChartOptions, setChartOptions } from "./chart-options.js";
|
|
6
9
|
|
|
7
10
|
const LEGEND_LABELS = { default: "Default", none: "None", top: "Top", bottom: "Bottom", left: "Left", right: "Right" };
|
|
8
11
|
const CONTENT_LABELS = { category: "Category", value: "Value", percent: "Percent" };
|
|
9
12
|
const POSITION_LABELS = { auto: "Automatic", center: "Center", "inside-end": "Inside end", "inside-base": "Inside base", "outside-end": "Outside end", above: "Above", below: "Below", left: "Left", right: "Right" };
|
|
10
13
|
|
|
11
14
|
let counter = 0;
|
|
15
|
+
const escapeHtml = (value) => String(value).replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]);
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* Mount the panel in `host`. `getSelectedPath()` returns the selected OPF path (any path inside a chart selects it);
|
|
@@ -16,7 +20,6 @@ let counter = 0;
|
|
|
16
20
|
* call `refresh()` when the selection changes (the panel also refreshes on every editor change).
|
|
17
21
|
*/
|
|
18
22
|
export function createChartOptionsPanel(host, { editor, getSelectedPath, onStatus = () => {} }) {
|
|
19
|
-
if (!chartOptionsAvailable()) return { refresh() {}, destroy() {}, element: undefined };
|
|
20
23
|
const id = `opf-chart-options-${++counter}`;
|
|
21
24
|
const root = document.createElement("section");
|
|
22
25
|
root.className = "opf-chart-options";
|
|
@@ -24,10 +27,16 @@ export function createChartOptionsPanel(host, { editor, getSelectedPath, onStatu
|
|
|
24
27
|
root.hidden = true;
|
|
25
28
|
root.innerHTML = `
|
|
26
29
|
<h3 class="opf-co-title">Chart options</h3>
|
|
30
|
+
<div class="opf-co-group" data-group="alt">
|
|
31
|
+
<label class="opf-co-row"><span>Alt text</span><input type="text" id="${id}-alt" aria-label="Chart alt text" data-opf-chart-option="alt" autocomplete="off" placeholder="What the chart shows"></label>
|
|
32
|
+
<label class="opf-co-row opf-co-check"><input type="checkbox" id="${id}-decorative" aria-label="Decorative chart" data-opf-chart-option="alt.decorative"><span>Decorative (no alt text)</span></label>
|
|
33
|
+
</div>
|
|
27
34
|
<div class="opf-co-group" data-group="axisTitles">
|
|
28
35
|
<label class="opf-co-row" data-axis="category"><span>Category axis title</span><input type="text" id="${id}-category" aria-label="Category axis title" data-opf-chart-option="axisTitles.category" autocomplete="off"></label>
|
|
29
36
|
<label class="opf-co-row" data-axis="value"><span>Value axis title</span><input type="text" id="${id}-value" aria-label="Value axis title" data-opf-chart-option="axisTitles.value" autocomplete="off"></label>
|
|
37
|
+
<label class="opf-co-row" data-axis="secondary"><span>Secondary axis title</span><input type="text" id="${id}-secondary" aria-label="Secondary axis title" data-opf-chart-option="axisTitles.secondary" autocomplete="off"></label>
|
|
30
38
|
</div>
|
|
39
|
+
<fieldset class="opf-co-group opf-co-series" data-group="combo"><legend>Series</legend><div data-row="combo-series"></div></fieldset>
|
|
31
40
|
<div class="opf-co-group" data-group="legend">
|
|
32
41
|
<label class="opf-co-row"><span>Legend</span><select id="${id}-legend" aria-label="Legend" data-opf-chart-option="legend">${CHART_LEGEND_POSITIONS.map((value) => `<option value="${value}">${LEGEND_LABELS[value]}</option>`).join("")}</select></label>
|
|
33
42
|
</div>
|
|
@@ -36,12 +45,18 @@ export function createChartOptionsPanel(host, { editor, getSelectedPath, onStatu
|
|
|
36
45
|
<fieldset class="opf-co-content"><legend>Label shows</legend>${CHART_LABEL_CONTENT.map((part) => `<label class="opf-co-check"><input type="checkbox" value="${part}" aria-label="Label shows ${CONTENT_LABELS[part].toLowerCase()}" data-opf-chart-option="dataLabels.content.${part}"><span>${CONTENT_LABELS[part]}</span></label>`).join("")}</fieldset>
|
|
37
46
|
<label class="opf-co-row" data-row="position"><span>Label position</span><select id="${id}-position" aria-label="Label position" data-opf-chart-option="dataLabels.position"></select></label>
|
|
38
47
|
<label class="opf-co-row" data-row="separator"><span>Separator</span><input type="text" id="${id}-separator" aria-label="Label separator" data-opf-chart-option="dataLabels.separator" autocomplete="off"></label>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="opf-co-group" data-group="highlight">
|
|
50
|
+
<fieldset class="opf-co-highlight" data-part="series"><legend>Highlight series</legend><div class="opf-co-choices" data-choices="series"></div></fieldset>
|
|
51
|
+
<fieldset class="opf-co-highlight" data-part="categories"><legend>Highlight categories</legend><div class="opf-co-choices" data-choices="categories"></div></fieldset>
|
|
39
52
|
</div>`;
|
|
40
53
|
host.append(root);
|
|
41
54
|
const $ = (selector) => root.querySelector(selector);
|
|
42
55
|
const fields = {
|
|
43
|
-
category: $(`#${id}-category`), value: $(`#${id}-value`), legend: $(`#${id}-legend`), labels: $(`#${id}-labels`),
|
|
56
|
+
alt: $(`#${id}-alt`), decorative: $(`#${id}-decorative`), category: $(`#${id}-category`), value: $(`#${id}-value`), secondary: $(`#${id}-secondary`), legend: $(`#${id}-legend`), labels: $(`#${id}-labels`),
|
|
57
|
+
series: $("[data-row='combo-series']"),
|
|
44
58
|
position: $(`#${id}-position`), separator: $(`#${id}-separator`), contents: [...root.querySelectorAll("[data-opf-chart-option^='dataLabels.content.']")],
|
|
59
|
+
highlight: { series: $("[data-choices='series']"), categories: $("[data-choices='categories']") },
|
|
45
60
|
};
|
|
46
61
|
let chartPath;
|
|
47
62
|
|
|
@@ -55,13 +70,34 @@ export function createChartOptionsPanel(host, { editor, getSelectedPath, onStatu
|
|
|
55
70
|
return;
|
|
56
71
|
}
|
|
57
72
|
chartPath = path;
|
|
58
|
-
const { fields: support, state } = readChartOptions(chart);
|
|
73
|
+
const { fields: support, state, choices, combo } = readChartOptions(chart, editor.presentation);
|
|
59
74
|
root.hidden = false;
|
|
60
75
|
const setValue = (input, value) => { if (input !== document.activeElement && input.value !== value) input.value = value; };
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
setValue(fields.alt, state.alt);
|
|
77
|
+
fields.decorative.checked = state.decorative;
|
|
78
|
+
fields.alt.disabled = state.decorative;
|
|
79
|
+
const secondaryInUse = Boolean(combo?.series.some((entry) => entry.axis === "secondary"));
|
|
80
|
+
for (const axis of ["category", "value", "secondary"]) {
|
|
81
|
+
$(`[data-axis="${axis}"]`).hidden = !support.axisTitles[axis] || (axis === "secondary" && !secondaryInUse);
|
|
63
82
|
setValue(fields[axis], state.axisTitles[axis]);
|
|
64
83
|
}
|
|
84
|
+
// FA-15: one row per plotted series of a combo chart: "Line" and, for a line, "Secondary axis".
|
|
85
|
+
$("[data-group='combo']").hidden = !combo;
|
|
86
|
+
const series = combo?.series ?? [];
|
|
87
|
+
const signature = JSON.stringify(series.map((entry) => entry.name));
|
|
88
|
+
if (fields.series.dataset.series !== signature) {
|
|
89
|
+
fields.series.innerHTML = series.map((entry, index) => `<div class="opf-co-row opf-co-series-row" data-series="${index}"><span>${escapeHtml(entry.name)}</span>` +
|
|
90
|
+
`<label class="opf-co-check"><input type="checkbox" data-opf-chart-option="line" value="${escapeHtml(entry.name)}" aria-label="${escapeHtml(entry.name)} as a line"><span>Line</span></label>` +
|
|
91
|
+
`<label class="opf-co-check"><input type="checkbox" data-opf-chart-option="secondaryAxis" value="${escapeHtml(entry.name)}" aria-label="${escapeHtml(entry.name)} on the secondary axis"><span>Secondary axis</span></label></div>`).join("");
|
|
92
|
+
fields.series.dataset.series = signature;
|
|
93
|
+
}
|
|
94
|
+
series.forEach((entry, index) => {
|
|
95
|
+
const row = fields.series.querySelector(`[data-series="${index}"]`);
|
|
96
|
+
const line = row.querySelector("[data-opf-chart-option='line']"), secondary = row.querySelector("[data-opf-chart-option='secondaryAxis']");
|
|
97
|
+
line.checked = entry.role === "line";
|
|
98
|
+
secondary.checked = entry.axis === "secondary";
|
|
99
|
+
secondary.disabled = entry.role !== "line";
|
|
100
|
+
});
|
|
65
101
|
$("[data-group='axisTitles']").hidden = !support.axisTitles.category && !support.axisTitles.value;
|
|
66
102
|
$("[data-group='legend']").hidden = !support.legend;
|
|
67
103
|
fields.legend.value = state.legend;
|
|
@@ -84,6 +120,31 @@ export function createChartOptionsPanel(host, { editor, getSelectedPath, onStatu
|
|
|
84
120
|
setValue(fields.separator, state.dataLabels.separator);
|
|
85
121
|
fields.separator.disabled = off || state.dataLabels.content.length < 2;
|
|
86
122
|
$("[data-row='separator']").hidden = support.dataLabels.content.length < 2;
|
|
123
|
+
// Highlight: one checkbox per plotted series and per category label the chart type can highlight.
|
|
124
|
+
for (const part of ["series", "categories"]) {
|
|
125
|
+
const offered = support.highlight[part] && choices[part].length > 0;
|
|
126
|
+
$(`[data-part='${part}']`).hidden = !offered;
|
|
127
|
+
const list = fields.highlight[part];
|
|
128
|
+
if (!offered) continue;
|
|
129
|
+
if (list.dataset.options !== JSON.stringify(choices[part])) {
|
|
130
|
+
list.replaceChildren(...choices[part].map((name) => {
|
|
131
|
+
const label = document.createElement("label");
|
|
132
|
+
label.className = "opf-co-check";
|
|
133
|
+
const input = document.createElement("input");
|
|
134
|
+
input.type = "checkbox";
|
|
135
|
+
input.value = name;
|
|
136
|
+
input.setAttribute("aria-label", `Highlight ${part === "series" ? "series" : "category"} ${name}`);
|
|
137
|
+
input.setAttribute("data-opf-chart-option", `highlight.${part}`);
|
|
138
|
+
const text = document.createElement("span");
|
|
139
|
+
text.textContent = name;
|
|
140
|
+
label.append(input, text);
|
|
141
|
+
return label;
|
|
142
|
+
}));
|
|
143
|
+
list.dataset.options = JSON.stringify(choices[part]);
|
|
144
|
+
}
|
|
145
|
+
for (const input of list.querySelectorAll("input")) input.checked = state.highlight[part].includes(input.value);
|
|
146
|
+
}
|
|
147
|
+
$("[data-group='highlight']").hidden = $("[data-part='series']").hidden && $("[data-part='categories']").hidden;
|
|
87
148
|
}
|
|
88
149
|
|
|
89
150
|
function commit(change, message) {
|
|
@@ -97,14 +158,27 @@ export function createChartOptionsPanel(host, { editor, getSelectedPath, onStatu
|
|
|
97
158
|
refresh();
|
|
98
159
|
}
|
|
99
160
|
|
|
161
|
+
const checkedNames = (part) => [...fields.highlight[part].querySelectorAll("input:checked")].map((input) => input.value);
|
|
100
162
|
const selectedContent = () => fields.contents.filter((input) => input.checked && !input.closest("label").hidden).map((input) => input.value);
|
|
101
163
|
const listeners = [
|
|
164
|
+
[fields.alt, "change", () => commit({ alt: fields.alt.value }, "Changed the chart's alt text. Undo restores it.")],
|
|
165
|
+
[fields.decorative, "change", () => commit({ decorative: fields.decorative.checked }, fields.decorative.checked ? "Marked the chart decorative. Undo restores it." : "The chart is no longer decorative. Undo restores it.")],
|
|
102
166
|
[fields.category, "change", () => commit({ axisTitles: { category: fields.category.value } }, "Changed the category axis title. Undo restores it.")],
|
|
103
167
|
[fields.value, "change", () => commit({ axisTitles: { value: fields.value.value } }, "Changed the value axis title. Undo restores it.")],
|
|
168
|
+
[fields.secondary, "change", () => commit({ axisTitles: { secondary: fields.secondary.value } }, "Changed the secondary axis title. Undo restores it.")],
|
|
169
|
+
[fields.series, "change", () => {
|
|
170
|
+
const checked = (option) => [...fields.series.querySelectorAll(`[data-opf-chart-option='${option}']`)].filter((input) => input.checked).map((input) => input.value);
|
|
171
|
+
const line = checked("line");
|
|
172
|
+
commit({ line, secondaryAxis: checked("secondaryAxis").filter((name) => line.includes(name)) }, "Changed which series are lines. Undo restores them.");
|
|
173
|
+
}],
|
|
104
174
|
[fields.legend, "change", () => commit({ legend: fields.legend.value }, "Changed the legend. Undo restores it.")],
|
|
105
175
|
[fields.labels, "change", () => commit({ dataLabels: fields.labels.checked }, fields.labels.checked ? "Showing data labels. Undo restores the chart." : "Hid the data labels. Undo restores them.")],
|
|
106
176
|
[fields.position, "change", () => commit({ dataLabels: { position: fields.position.value } }, "Moved the data labels. Undo restores them.")],
|
|
107
177
|
[fields.separator, "change", () => commit({ dataLabels: { separator: fields.separator.value } }, "Changed the label separator. Undo restores it.")],
|
|
178
|
+
...["series", "categories"].map((part) => [fields.highlight[part], "change", () => {
|
|
179
|
+
const names = checkedNames(part);
|
|
180
|
+
commit({ highlight: { [part]: names } }, names.length ? `Highlighted ${names.join(", ")}. Undo restores the colors.` : `Cleared the ${part} highlight. Undo restores it.`);
|
|
181
|
+
}]),
|
|
108
182
|
...fields.contents.map((input) => [input, "change", () => {
|
|
109
183
|
const content = selectedContent();
|
|
110
184
|
// A label always shows something: unticking the last part keeps it.
|