logisheets-core 1.15.1 → 1.16.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/README.md +2 -2
- package/dist/craft/events.js +2 -1
- package/dist/craft/manifest.d.ts +5 -2
- package/dist/craft/runtime.d.ts +14 -0
- package/dist/craft/state.d.ts +12 -0
- package/dist/craft/state.js +12 -8
- package/dist/craft-interactions/index.d.ts +17 -0
- package/dist/craft-interactions/index.js +37 -0
- package/dist/format/index.d.ts +35 -0
- package/dist/format/index.js +24 -0
- package/dist/index.js +19 -1
- package/dist/ops/index.d.ts +37 -5
- package/dist/ops/index.js +37 -5
- package/dist/permissions/index.d.ts +12 -0
- package/dist/permissions/index.js +17 -5
- package/dist/port.js +5 -3
- package/dist/utils/a1notation.d.ts +7 -0
- package/dist/utils/a1notation.js +7 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,8 +35,8 @@ declare const client: Client // supplied by the host (browser worker or Node)
|
|
|
35
35
|
const ops = new WorkbookOps(client)
|
|
36
36
|
|
|
37
37
|
await ops.inputCell(0, 0, 0, 'Hello') // sheet 0, cell A1
|
|
38
|
-
await ops.createSheet(
|
|
39
|
-
await ops.setSheetColor(0, '#
|
|
38
|
+
await ops.createSheet('Summary', 1)
|
|
39
|
+
await ops.setSheetColor(0, 'FF4472C4') // ARGB hex, no '#'
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
`WorkbookOps` covers cell input (including block cells and cell images), sheet
|
package/dist/craft/events.js
CHANGED
|
@@ -60,7 +60,8 @@ export function dispatchCraftCanvasEvent(evt) {
|
|
|
60
60
|
return { handled: false };
|
|
61
61
|
try {
|
|
62
62
|
const r = handler(evt);
|
|
63
|
-
const handled = r === true ||
|
|
63
|
+
const handled = r === true ||
|
|
64
|
+
(typeof r === 'object' && r !== null && r.handled === true);
|
|
64
65
|
return { handled };
|
|
65
66
|
}
|
|
66
67
|
catch (e) {
|
package/dist/craft/manifest.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
/** Where a craft's code lives. Resolved by a host's `CraftRegistry`. */
|
|
1
2
|
export interface CraftManifest {
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Locator of the headless runtime JS, interpreted by the registry (a URL
|
|
5
|
+
* for logisheets-runtime's HttpCraftRegistry). Empty means the craft has
|
|
6
|
+
* no runtime and a headless host skips it.
|
|
4
7
|
*/
|
|
5
8
|
readonly rtJs: string;
|
|
6
9
|
/**
|
|
7
|
-
*
|
|
10
|
+
* URL of the craft's browser UI page (loaded in the craft panel iframe).
|
|
8
11
|
*/
|
|
9
12
|
readonly html: string;
|
|
10
13
|
}
|
package/dist/craft/runtime.d.ts
CHANGED
|
@@ -31,7 +31,16 @@ export type MaybePromise<T> = T | Promise<T>;
|
|
|
31
31
|
* onResponse the response has been produced and is about to be returned
|
|
32
32
|
*/
|
|
33
33
|
export interface CraftRuntime<S extends CraftState = CraftState, W = unknown> {
|
|
34
|
+
/**
|
|
35
|
+
* Rehydrate from the state the craft saved. Returning an `ErrorMessage`
|
|
36
|
+
* makes the host skip this craft. `s` is the parsed state object; the
|
|
37
|
+
* host passes the SAME object to every later hook.
|
|
38
|
+
*/
|
|
34
39
|
onLoad: (s: S, wb: W) => MaybePromise<Result<void>>;
|
|
40
|
+
/**
|
|
41
|
+
* Write the request's inputs into the workbook. Return an `ErrorMessage`
|
|
42
|
+
* (or throw) to object; the host then rejects the request.
|
|
43
|
+
*/
|
|
35
44
|
onRequest: (req: JsonRpcRequest, s: S, wb: W) => MaybePromise<Result<void>>;
|
|
36
45
|
/**
|
|
37
46
|
* Called once an RPC request's inputs have been written into the workbook
|
|
@@ -42,5 +51,10 @@ export interface CraftRuntime<S extends CraftState = CraftState, W = unknown> {
|
|
|
42
51
|
* input. Optional: a craft with no validation needs simply omits it.
|
|
43
52
|
*/
|
|
44
53
|
onValidate?: (s: S, wb: W) => MaybePromise<Result<readonly Violation[]>>;
|
|
54
|
+
/**
|
|
55
|
+
* Fill `resp` (typically `resp.result`) from the workbook. It is mutated
|
|
56
|
+
* in place: the host returns the same object. Return an `ErrorMessage`
|
|
57
|
+
* (or throw) to turn the reply into an error.
|
|
58
|
+
*/
|
|
45
59
|
onResponse: (resp: JsonRpcResponse, s: S, wb: W) => MaybePromise<Result<void>>;
|
|
46
60
|
}
|
package/dist/craft/state.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
/** Push a craft's serialized state (replaces the previous one). Called from
|
|
2
|
+
* the iframe-injected `setCraftState`; `craftId` is captured per-iframe by
|
|
3
|
+
* the host. An empty `craftId` is ignored. Not written to the engine until
|
|
4
|
+
* the host next saves the file. */
|
|
1
5
|
export declare function setCraftState(craftId: string, json: string): void;
|
|
6
|
+
/** A craft's last-known state (e.g. the one rehydrated from the loaded
|
|
7
|
+
* workbook), or undefined if the craft never stored anything. */
|
|
2
8
|
export declare function getCraftState(craftId: string): string | undefined;
|
|
9
|
+
/** Forget a craft's state; the next save omits it. */
|
|
3
10
|
export declare function clearCraftState(craftId: string): void;
|
|
11
|
+
/** Snapshot every craft's state, for the host to fold into the AppData
|
|
12
|
+
* envelope's `craftStates` on save. */
|
|
4
13
|
export declare function getPersistentCraftStates(): Record<string, string>;
|
|
14
|
+
/** Rehydrate from a previously-persisted snapshot. Replaces the current
|
|
15
|
+
* contents, so a freshly-loaded workbook doesn't inherit state from a prior
|
|
16
|
+
* one; non-string entries are dropped. Pass `undefined` to reset. */
|
|
5
17
|
export declare function loadPersistentCraftStates(data: unknown): void;
|
package/dist/craft/state.js
CHANGED
|
@@ -12,31 +12,35 @@
|
|
|
12
12
|
// undo/redo Status (it rides AppData, a side channel on the workbook), so
|
|
13
13
|
// writing it never pollutes edit history.
|
|
14
14
|
const craftStates = new Map();
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/** Push a craft's serialized state (replaces the previous one). Called from
|
|
16
|
+
* the iframe-injected `setCraftState`; `craftId` is captured per-iframe by
|
|
17
|
+
* the host. An empty `craftId` is ignored. Not written to the engine until
|
|
18
|
+
* the host next saves the file. */
|
|
17
19
|
export function setCraftState(craftId, json) {
|
|
18
20
|
if (!craftId)
|
|
19
21
|
return;
|
|
20
22
|
craftStates.set(craftId, json);
|
|
21
23
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
/** A craft's last-known state (e.g. the one rehydrated from the loaded
|
|
25
|
+
* workbook), or undefined if the craft never stored anything. */
|
|
24
26
|
export function getCraftState(craftId) {
|
|
25
27
|
return craftStates.get(craftId);
|
|
26
28
|
}
|
|
29
|
+
/** Forget a craft's state; the next save omits it. */
|
|
27
30
|
export function clearCraftState(craftId) {
|
|
28
31
|
craftStates.delete(craftId);
|
|
29
32
|
}
|
|
30
|
-
|
|
33
|
+
/** Snapshot every craft's state, for the host to fold into the AppData
|
|
34
|
+
* envelope's `craftStates` on save. */
|
|
31
35
|
export function getPersistentCraftStates() {
|
|
32
36
|
const out = {};
|
|
33
37
|
for (const [craftId, json] of craftStates)
|
|
34
38
|
out[craftId] = json;
|
|
35
39
|
return out;
|
|
36
40
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
/** Rehydrate from a previously-persisted snapshot. Replaces the current
|
|
42
|
+
* contents, so a freshly-loaded workbook doesn't inherit state from a prior
|
|
43
|
+
* one; non-string entries are dropped. Pass `undefined` to reset. */
|
|
40
44
|
export function loadPersistentCraftStates(data) {
|
|
41
45
|
craftStates.clear();
|
|
42
46
|
if (!data || typeof data !== 'object')
|
|
@@ -7,11 +7,16 @@ export interface RadioBinding {
|
|
|
7
7
|
col: number;
|
|
8
8
|
value: string;
|
|
9
9
|
}
|
|
10
|
+
/** Listen for ANY change to the interaction store (every widget kind, not
|
|
11
|
+
* just radios). Returns an unsubscribe function. */
|
|
10
12
|
export declare function subscribeRadioBindings(listener: () => void): () => void;
|
|
11
13
|
export declare function registerRadioBinding(binding: RadioBinding): void;
|
|
12
14
|
export declare function unregisterRadioBinding(binding: Pick<RadioBinding, 'groupId' | 'blockId' | 'row' | 'col'>): void;
|
|
15
|
+
/** Remove a group's radio bindings and selection, or every group's when
|
|
16
|
+
* `groupId` is omitted. */
|
|
13
17
|
export declare function clearRadioBindings(groupId?: string): void;
|
|
14
18
|
export declare function getRadioBindings(): readonly RadioBinding[];
|
|
19
|
+
/** The `value` of the group's selected binding, if any. */
|
|
15
20
|
export declare function getRadioSelection(groupId: string): string | undefined;
|
|
16
21
|
export declare function setRadioSelection(groupId: string, value: string): void;
|
|
17
22
|
export interface MultiSelectBinding {
|
|
@@ -27,9 +32,13 @@ export declare function registerMultiSelectBinding(binding: MultiSelectBinding):
|
|
|
27
32
|
export declare function unregisterMultiSelectBinding(binding: Pick<MultiSelectBinding, 'groupId' | 'blockId' | 'row' | 'col'>): void;
|
|
28
33
|
export declare function clearMultiSelectBindings(groupId?: string): void;
|
|
29
34
|
export declare function getMultiSelectBindings(): readonly MultiSelectBinding[];
|
|
35
|
+
/** Cap a group's selections (negative clamps to 0). Until set, a group has
|
|
36
|
+
* no cap when toggling, though {@link getMultiSelectMax} reports 0. */
|
|
30
37
|
export declare function setMultiSelectMax(groupId: string, max: number): void;
|
|
31
38
|
export declare function getMultiSelectMax(groupId: string): number;
|
|
32
39
|
export declare function getMultiSelectSelections(groupId: string): string[];
|
|
40
|
+
/** Select `value` if unselected and under the cap, deselect it if
|
|
41
|
+
* selected. A select past the cap is silently ignored. */
|
|
33
42
|
export declare function toggleMultiSelectValue(groupId: string, value: string): void;
|
|
34
43
|
export interface PointAllocatorBinding {
|
|
35
44
|
type: 'pointAllocator';
|
|
@@ -49,6 +58,8 @@ export declare function registerPointAllocator(binding: PointAllocatorBinding):
|
|
|
49
58
|
export declare function unregisterPointAllocator(binding: Pick<PointAllocatorBinding, 'groupId' | 'blockId' | 'row' | 'col'>): void;
|
|
50
59
|
export declare function clearPointAllocators(groupId?: string): void;
|
|
51
60
|
export declare function getPointAllocatorBindings(): readonly PointAllocatorBinding[];
|
|
61
|
+
/** Set a group's point budget (negative clamps to 0). Shrinking it does not
|
|
62
|
+
* take back points already allocated, so `remaining` can go negative. */
|
|
52
63
|
export declare function setPointPool(groupId: string, total: number): void;
|
|
53
64
|
export declare function getPointPool(groupId: string): {
|
|
54
65
|
total: number;
|
|
@@ -73,6 +84,8 @@ export declare function registerNumberSlider(binding: NumberSliderBinding): void
|
|
|
73
84
|
export declare function unregisterNumberSlider(binding: Pick<NumberSliderBinding, 'groupId' | 'blockId' | 'row' | 'col'>): void;
|
|
74
85
|
export declare function clearNumberSliders(groupId?: string): void;
|
|
75
86
|
export declare function getNumberSliderBindings(): readonly NumberSliderBinding[];
|
|
87
|
+
/** Add `delta` points to one cell (negative removes), clamped so the group
|
|
88
|
+
* never exceeds its pool and a cell never goes below 0. */
|
|
76
89
|
export declare function adjustPointAllocation(binding: Pick<PointAllocatorBinding, 'groupId' | 'blockId' | 'row' | 'col'>, delta: number): void;
|
|
77
90
|
export interface PercentAllocatorBinding {
|
|
78
91
|
type: 'percentAllocator';
|
|
@@ -135,6 +148,10 @@ interface PersistedShape {
|
|
|
135
148
|
numberSliderBindings: NumberSliderBinding[];
|
|
136
149
|
percentBindings: PercentAllocatorBinding[];
|
|
137
150
|
}
|
|
151
|
+
/** Snapshot every binding and user selection, for the host to fold into the
|
|
152
|
+
* AppData envelope's `craftInteractions` on save. */
|
|
138
153
|
export declare function getPersistentInteractions(): PersistedShape;
|
|
154
|
+
/** Replace the whole store from a snapshot (as written by
|
|
155
|
+
* {@link getPersistentInteractions}); malformed entries are dropped. */
|
|
139
156
|
export declare function loadPersistentInteractions(data: unknown): void;
|
|
140
157
|
export {};
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
// Craft interactions — host-drawn widgets bound to block cells.
|
|
2
|
+
//
|
|
3
|
+
// A craft registers bindings (radio, multi-select, point allocator, number
|
|
4
|
+
// slider, percent allocator); the host draws an overlay on each bound cell and
|
|
5
|
+
// records what the user picks. The browser injects these functions onto the
|
|
6
|
+
// craft iframe (`window.registerRadio`, `window.getRadioSelection`, ...; see
|
|
7
|
+
// the app's craft-interaction layer), and the craft reads results back on
|
|
8
|
+
// demand. Nothing here writes to the workbook.
|
|
9
|
+
//
|
|
10
|
+
// Conventions for every binding:
|
|
11
|
+
// - `row` / `col` are BLOCK-relative, counted from the block's master cell,
|
|
12
|
+
// so a binding follows its block when the block moves.
|
|
13
|
+
// - A binding is keyed by (groupId, blockId, row, col); registering the same
|
|
14
|
+
// key again replaces it. `sheetIdx` is NOT part of the key, so the same
|
|
15
|
+
// key on two sheets collides.
|
|
16
|
+
// - State is one module-level store per process; subscribers
|
|
17
|
+
// ({@link subscribeRadioBindings}) hear about every change of every kind.
|
|
18
|
+
// - Radio / multi-select / point selections live HERE, not in cells, and
|
|
19
|
+
// persist through {@link getPersistentInteractions} on save. Slider and
|
|
20
|
+
// percent values live in the cells themselves.
|
|
1
21
|
const radioBindings = new Map();
|
|
2
22
|
const groupSelections = new Map();
|
|
3
23
|
const storeListeners = new Set();
|
|
@@ -8,6 +28,8 @@ function notifyStore() {
|
|
|
8
28
|
for (const l of storeListeners)
|
|
9
29
|
l();
|
|
10
30
|
}
|
|
31
|
+
/** Listen for ANY change to the interaction store (every widget kind, not
|
|
32
|
+
* just radios). Returns an unsubscribe function. */
|
|
11
33
|
export function subscribeRadioBindings(listener) {
|
|
12
34
|
storeListeners.add(listener);
|
|
13
35
|
return () => {
|
|
@@ -22,6 +44,8 @@ export function unregisterRadioBinding(binding) {
|
|
|
22
44
|
radioBindings.delete(bindingKey(binding));
|
|
23
45
|
notifyStore();
|
|
24
46
|
}
|
|
47
|
+
/** Remove a group's radio bindings and selection, or every group's when
|
|
48
|
+
* `groupId` is omitted. */
|
|
25
49
|
export function clearRadioBindings(groupId) {
|
|
26
50
|
if (groupId === undefined) {
|
|
27
51
|
radioBindings.clear();
|
|
@@ -39,6 +63,7 @@ export function clearRadioBindings(groupId) {
|
|
|
39
63
|
export function getRadioBindings() {
|
|
40
64
|
return Array.from(radioBindings.values());
|
|
41
65
|
}
|
|
66
|
+
/** The `value` of the group's selected binding, if any. */
|
|
42
67
|
export function getRadioSelection(groupId) {
|
|
43
68
|
return groupSelections.get(groupId);
|
|
44
69
|
}
|
|
@@ -79,6 +104,8 @@ export function clearMultiSelectBindings(groupId) {
|
|
|
79
104
|
export function getMultiSelectBindings() {
|
|
80
105
|
return Array.from(multiSelectBindings.values());
|
|
81
106
|
}
|
|
107
|
+
/** Cap a group's selections (negative clamps to 0). Until set, a group has
|
|
108
|
+
* no cap when toggling, though {@link getMultiSelectMax} reports 0. */
|
|
82
109
|
export function setMultiSelectMax(groupId, max) {
|
|
83
110
|
multiSelectMax.set(groupId, Math.max(0, max));
|
|
84
111
|
notifyStore();
|
|
@@ -89,6 +116,8 @@ export function getMultiSelectMax(groupId) {
|
|
|
89
116
|
export function getMultiSelectSelections(groupId) {
|
|
90
117
|
return Array.from(multiSelectSelections.get(groupId) ?? []);
|
|
91
118
|
}
|
|
119
|
+
/** Select `value` if unselected and under the cap, deselect it if
|
|
120
|
+
* selected. A select past the cap is silently ignored. */
|
|
92
121
|
export function toggleMultiSelectValue(groupId, value) {
|
|
93
122
|
let set = multiSelectSelections.get(groupId);
|
|
94
123
|
if (!set) {
|
|
@@ -151,6 +180,8 @@ export function clearPointAllocators(groupId) {
|
|
|
151
180
|
export function getPointAllocatorBindings() {
|
|
152
181
|
return Array.from(pointBindings.values());
|
|
153
182
|
}
|
|
183
|
+
/** Set a group's point budget (negative clamps to 0). Shrinking it does not
|
|
184
|
+
* take back points already allocated, so `remaining` can go negative. */
|
|
154
185
|
export function setPointPool(groupId, total) {
|
|
155
186
|
pointPoolTotals.set(groupId, Math.max(0, total));
|
|
156
187
|
notifyStore();
|
|
@@ -203,6 +234,8 @@ export function clearNumberSliders(groupId) {
|
|
|
203
234
|
export function getNumberSliderBindings() {
|
|
204
235
|
return Array.from(numberSliderBindings.values());
|
|
205
236
|
}
|
|
237
|
+
/** Add `delta` points to one cell (negative removes), clamped so the group
|
|
238
|
+
* never exceeds its pool and a cell never goes below 0. */
|
|
206
239
|
export function adjustPointAllocation(binding, delta) {
|
|
207
240
|
const { groupId, blockId, row, col } = binding;
|
|
208
241
|
let m = pointAllocations.get(groupId);
|
|
@@ -328,6 +361,8 @@ export function redistributePercent(current, targetIdx, deltaPct) {
|
|
|
328
361
|
}
|
|
329
362
|
return next;
|
|
330
363
|
}
|
|
364
|
+
/** Snapshot every binding and user selection, for the host to fold into the
|
|
365
|
+
* AppData envelope's `craftInteractions` on save. */
|
|
331
366
|
export function getPersistentInteractions() {
|
|
332
367
|
const out = {
|
|
333
368
|
radioBindings: Array.from(radioBindings.values()),
|
|
@@ -361,6 +396,8 @@ export function getPersistentInteractions() {
|
|
|
361
396
|
}
|
|
362
397
|
return out;
|
|
363
398
|
}
|
|
399
|
+
/** Replace the whole store from a snapshot (as written by
|
|
400
|
+
* {@link getPersistentInteractions}); malformed entries are dropped. */
|
|
364
401
|
export function loadPersistentInteractions(data) {
|
|
365
402
|
// Always clear first, then repopulate from whatever the workbook carried.
|
|
366
403
|
// Passing undefined/null (a workbook with no interaction state) therefore
|
package/dist/format/index.d.ts
CHANGED
|
@@ -1,23 +1,58 @@
|
|
|
1
1
|
import type { Alignment, Payload, StPatternType, StBorderStyle, SelectedData } from 'logisheets-web';
|
|
2
|
+
/** A partial font change: only the fields present are applied. */
|
|
2
3
|
export interface FontStyle {
|
|
3
4
|
bold?: boolean;
|
|
5
|
+
/** `true` sets a single underline, `false` removes it. */
|
|
4
6
|
underline?: boolean;
|
|
5
7
|
italic?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* 8-hex-digit ARGB with NO `#`, e.g. `'FF1976D2'`. Passed to the engine
|
|
10
|
+
* unchanged: a `#RRGGBB` or 6-digit value yields no color and silently
|
|
11
|
+
* does not render. `''` is ignored rather than clearing the color.
|
|
12
|
+
*/
|
|
6
13
|
color?: string;
|
|
14
|
+
/** Point size. `0` is ignored. */
|
|
7
15
|
size?: number;
|
|
8
16
|
strike?: boolean;
|
|
9
17
|
/** Font family name, e.g. "Arial", "Times New Roman", "Microsoft YaHei". */
|
|
10
18
|
name?: string;
|
|
11
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Font payloads for the selection: one `cellStyleUpdate` per cell of a cell
|
|
22
|
+
* range, or one `lineStyleUpdate` for a row/column selection. `[]` when
|
|
23
|
+
* nothing is selected.
|
|
24
|
+
*/
|
|
12
25
|
export declare function generateFontPayload(sheetIdx: number, data: SelectedData, update: FontStyle): readonly Payload[];
|
|
26
|
+
/** Alignment payloads for the selection (same shape rules as
|
|
27
|
+
* {@link generateFontPayload}). */
|
|
13
28
|
export declare function generateAlgnmentPayload(sheetIdx: number, data: SelectedData, alignment: Alignment): readonly Payload[];
|
|
14
29
|
export declare function generateWrapTextPayload(sheetIdx: number, data: SelectedData, wrapText: boolean): readonly Payload[];
|
|
15
30
|
export declare function generateNumFmtPayload(sheetIdx: number, data: SelectedData, numFmt: string): readonly Payload[];
|
|
31
|
+
/**
|
|
32
|
+
* Pattern-fill payloads for the selection. Unlike font and border colors,
|
|
33
|
+
* these take hex strings (`'#RRGGBB'`, `'RRGGBB'` or `'AARRGGBB'`, alpha
|
|
34
|
+
* dropped) and convert them to the engine's `{red, green, blue}` 0-255
|
|
35
|
+
* objects. Omitted / empty colors leave that part of the fill unchanged.
|
|
36
|
+
*/
|
|
16
37
|
export declare function generatePatternFillPayload(sheetIdx: number, data: SelectedData, fgColor?: string, bgColor?: string, pattern?: StPatternType): readonly Payload[];
|
|
17
38
|
export type BatchUpdateType = 'all' | 'top' | 'bottom' | 'left' | 'right' | 'horizontal' | 'vertical' | 'outer' | 'inner' | 'clear';
|
|
39
|
+
/** Which edges of the selection to set, and how. */
|
|
18
40
|
export interface BorderBatchUpdate {
|
|
41
|
+
/** Which edges: one side, all/outer/inner, or the inner horizontal or
|
|
42
|
+
* vertical lines. `clear` currently generates no payloads at all. */
|
|
19
43
|
batch: BatchUpdateType;
|
|
44
|
+
/** 8-hex-digit ARGB, e.g. `'FF000000'`. A leading `#` is stripped and
|
|
45
|
+
* the value upper-cased, but a 6-digit value is NOT expanded and will
|
|
46
|
+
* not render. */
|
|
20
47
|
color?: string;
|
|
21
48
|
borderType?: StBorderStyle;
|
|
22
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Border payloads for the selection. Setting an edge also clears the facing
|
|
52
|
+
* edge of the neighbouring cell (its bottom for a top edge, and so on), so
|
|
53
|
+
* two conflicting borders never share one grid line.
|
|
54
|
+
*
|
|
55
|
+
* Mutates `update.color` in place (strips `#`, upper-cases); pass a copy if
|
|
56
|
+
* the caller keeps the object.
|
|
57
|
+
*/
|
|
23
58
|
export declare function generateBorderPayloads(sheetIdx: number, data: SelectedData, update: BorderBatchUpdate): readonly Payload[];
|
package/dist/format/index.js
CHANGED
|
@@ -18,6 +18,9 @@ function selectedCellRange(v) {
|
|
|
18
18
|
function selectedLines(v) {
|
|
19
19
|
return v.data?.ty === 'line' ? v.data.d : undefined;
|
|
20
20
|
}
|
|
21
|
+
// Fills are the one place the engine takes an `{red, green, blue}` object
|
|
22
|
+
// rather than an ARGB string, so the fill generator converts here. Accepts
|
|
23
|
+
// `#RRGGBB`, `RRGGBB` or 8-digit ARGB (alpha dropped).
|
|
21
24
|
function hexToColor(hex) {
|
|
22
25
|
let h = hex.startsWith('#') ? hex.slice(1) : hex;
|
|
23
26
|
// ARGB format (8 chars): skip the first 2 alpha chars
|
|
@@ -35,6 +38,11 @@ function cellStyle(sheetIdx, row, col, ty) {
|
|
|
35
38
|
function lineStyle(sheetIdx, from, to, row, ty) {
|
|
36
39
|
return { type: 'lineStyleUpdate', value: { sheetIdx, from, to, row, ty } };
|
|
37
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Font payloads for the selection: one `cellStyleUpdate` per cell of a cell
|
|
43
|
+
* range, or one `lineStyleUpdate` for a row/column selection. `[]` when
|
|
44
|
+
* nothing is selected.
|
|
45
|
+
*/
|
|
38
46
|
export function generateFontPayload(sheetIdx, data, update) {
|
|
39
47
|
if (!data.data)
|
|
40
48
|
return [];
|
|
@@ -79,6 +87,8 @@ export function generateFontPayload(sheetIdx, data, update) {
|
|
|
79
87
|
lineTy.setFontName = update.name;
|
|
80
88
|
return [lineStyle(sheetIdx, d.start, d.end, d.type === 'row', lineTy)];
|
|
81
89
|
}
|
|
90
|
+
/** Alignment payloads for the selection (same shape rules as
|
|
91
|
+
* {@link generateFontPayload}). */
|
|
82
92
|
export function generateAlgnmentPayload(sheetIdx, data, alignment) {
|
|
83
93
|
return generateForSelection(sheetIdx, data, { setAlignment: alignment });
|
|
84
94
|
}
|
|
@@ -88,6 +98,12 @@ export function generateWrapTextPayload(sheetIdx, data, wrapText) {
|
|
|
88
98
|
export function generateNumFmtPayload(sheetIdx, data, numFmt) {
|
|
89
99
|
return generateForSelection(sheetIdx, data, { setNumFmt: numFmt });
|
|
90
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Pattern-fill payloads for the selection. Unlike font and border colors,
|
|
103
|
+
* these take hex strings (`'#RRGGBB'`, `'RRGGBB'` or `'AARRGGBB'`, alpha
|
|
104
|
+
* dropped) and convert them to the engine's `{red, green, blue}` 0-255
|
|
105
|
+
* objects. Omitted / empty colors leave that part of the fill unchanged.
|
|
106
|
+
*/
|
|
91
107
|
export function generatePatternFillPayload(sheetIdx, data, fgColor, bgColor, pattern) {
|
|
92
108
|
const fill = {};
|
|
93
109
|
if (fgColor)
|
|
@@ -113,6 +129,14 @@ function generateForSelection(sheetIdx, data, ty) {
|
|
|
113
129
|
const d = data.data.d;
|
|
114
130
|
return [lineStyle(sheetIdx, d.start, d.end, d.type === 'row', ty)];
|
|
115
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Border payloads for the selection. Setting an edge also clears the facing
|
|
134
|
+
* edge of the neighbouring cell (its bottom for a top edge, and so on), so
|
|
135
|
+
* two conflicting borders never share one grid line.
|
|
136
|
+
*
|
|
137
|
+
* Mutates `update.color` in place (strips `#`, upper-cases); pass a copy if
|
|
138
|
+
* the caller keeps the object.
|
|
139
|
+
*/
|
|
116
140
|
export function generateBorderPayloads(sheetIdx, data, update) {
|
|
117
141
|
if (update.color) {
|
|
118
142
|
if (update.color.startsWith('#'))
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,25 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Runs unchanged in the browser app and in a Node runtime. It depends on
|
|
4
4
|
// logisheets-web for TYPES only (see ./port); the concrete engine Client is
|
|
5
|
-
// injected by the host
|
|
5
|
+
// injected by the host: logisheets-engine's worker client in the browser,
|
|
6
|
+
// logisheets-runtime's `handle()` proxy on Node. ESM only.
|
|
7
|
+
//
|
|
8
|
+
// Main exports:
|
|
9
|
+
// - WorkbookOps (./ops): the high-level operation layer. Every engine-facing
|
|
10
|
+
// operation a host offers (cell input, sheets, blocks, form schemas,
|
|
11
|
+
// analysis blocks, pivots, formatting, validation) lives here once.
|
|
12
|
+
// - format generators (./format): pure selection -> style payload builders.
|
|
13
|
+
// - craft contracts (./craft): CraftRuntime (the headless craft hooks),
|
|
14
|
+
// craft state (per-document, rides AppData), craft storage (per-device),
|
|
15
|
+
// canvas-input routing, the JSON-RPC wire types.
|
|
16
|
+
// - craft interactions (./craft-interactions): host-drawn widgets (radio,
|
|
17
|
+
// multi-select, allocators, sliders) crafts bind to block cells.
|
|
18
|
+
// - validation, field authoring model, Value helpers, permissions registry,
|
|
19
|
+
// string / A1 / type-guard utilities.
|
|
20
|
+
//
|
|
21
|
+
// Module-level stores (craft state, storage backend, interactions, active
|
|
22
|
+
// craft, callerRegistry) are process singletons: one workbook per host
|
|
23
|
+
// process is assumed. A Node host serving several workbooks shares them.
|
|
6
24
|
export * from './port.js';
|
|
7
25
|
export * from './ops/index.js';
|
|
8
26
|
export * from './format/index.js';
|
package/dist/ops/index.d.ts
CHANGED
|
@@ -218,6 +218,23 @@ export interface PivotRefresh {
|
|
|
218
218
|
/**
|
|
219
219
|
* High-level workbook operations bound to one engine {@link Client}.
|
|
220
220
|
* Construct one per workbook and share it across the host.
|
|
221
|
+
*
|
|
222
|
+
* Indexes are 0-based; `sheetIdx` is the tab position. Block-relative
|
|
223
|
+
* `row`/`col` count from the block's master (top-left) cell.
|
|
224
|
+
*
|
|
225
|
+
* Failure contract, read before relying on a write:
|
|
226
|
+
* - An operation THROWS (rejects) when the engine answers a call with an
|
|
227
|
+
* `ErrorMessage`, and when this layer refuses its inputs (documented per
|
|
228
|
+
* method).
|
|
229
|
+
* - An operation does NOT throw when the engine refuses the edit itself.
|
|
230
|
+
* The engine reports that as an `ActionEffect` with
|
|
231
|
+
* `status.type === 'err'` (reason in `errorMessage`) and applies nothing.
|
|
232
|
+
* Methods returning `Promise<ActionEffect>` hand that effect back: check
|
|
233
|
+
* `status`. Methods returning `Promise<void>` or a summary DROP it, so a
|
|
234
|
+
* refused transaction looks like success there.
|
|
235
|
+
*
|
|
236
|
+
* Every transaction is built at the injected {@link TempModeProvider}'s
|
|
237
|
+
* current value, so in the browser's temp mode these writes are speculative.
|
|
221
238
|
*/
|
|
222
239
|
export declare class WorkbookOps {
|
|
223
240
|
private readonly client;
|
|
@@ -228,7 +245,9 @@ export declare class WorkbookOps {
|
|
|
228
245
|
/**
|
|
229
246
|
* Build a transaction at the host's current temp-mode, send it, and return
|
|
230
247
|
* the engine's effect. Throws on an engine ErrorMessage so callers can use
|
|
231
|
-
* normal try/catch instead of inspecting a union.
|
|
248
|
+
* normal try/catch instead of inspecting a union. An engine REJECTION is
|
|
249
|
+
* not an ErrorMessage: it comes back as the effect with `status.type ===
|
|
250
|
+
* 'err'` and is returned, not thrown.
|
|
232
251
|
*/
|
|
233
252
|
private apply;
|
|
234
253
|
/** Write a value or formula into a cell. */
|
|
@@ -263,7 +282,11 @@ export declare class WorkbookOps {
|
|
|
263
282
|
renameSheet(oldName: string, newName: string): Promise<ActionEffect>;
|
|
264
283
|
/** Delete the sheet at index `idx`. */
|
|
265
284
|
deleteSheet(idx: number): Promise<ActionEffect>;
|
|
266
|
-
/**
|
|
285
|
+
/**
|
|
286
|
+
* Set a sheet tab's color: 8-hex ARGB with no `#` (e.g. `'FF4472C4'`),
|
|
287
|
+
* or `''` to clear. Stored verbatim and written to the file's `tabColor`;
|
|
288
|
+
* nothing validates or normalises it.
|
|
289
|
+
*/
|
|
267
290
|
setSheetColor(idx: number, color: string): Promise<ActionEffect>;
|
|
268
291
|
/** Insert `cnt` rows into a block, starting at block-row `start`. */
|
|
269
292
|
insertRowsInBlock(sheetIdx: number, blockId: number, start: number, cnt?: number): Promise<ActionEffect>;
|
|
@@ -298,7 +321,8 @@ export declare class WorkbookOps {
|
|
|
298
321
|
* re-`bindFormSchema` is needed.
|
|
299
322
|
*/
|
|
300
323
|
moveBlockLine(sheetIdx: number, blockId: number, from: number, to: number, isRow?: boolean, undoable?: boolean): Promise<ActionEffect>;
|
|
301
|
-
/** Apply font styling (bold/italic/underline/strike/color/size).
|
|
324
|
+
/** Apply font styling (bold/italic/underline/strike/color/size). The
|
|
325
|
+
* color is 8-hex ARGB with no `#`, see {@link FontStyle}. */
|
|
302
326
|
setFont(sheetIdx: number, data: SelectedData, update: FontStyle): Promise<void>;
|
|
303
327
|
/** Apply horizontal/vertical alignment. */
|
|
304
328
|
setAlignment(sheetIdx: number, data: SelectedData, alignment: Alignment): Promise<void>;
|
|
@@ -306,13 +330,16 @@ export declare class WorkbookOps {
|
|
|
306
330
|
setWrapText(sheetIdx: number, data: SelectedData, wrapText: boolean): Promise<void>;
|
|
307
331
|
/** Apply a number format. */
|
|
308
332
|
setNumFmt(sheetIdx: number, data: SelectedData, numFmt: string): Promise<void>;
|
|
309
|
-
/** Apply a pattern fill (foreground/background color + pattern).
|
|
333
|
+
/** Apply a pattern fill (foreground/background color + pattern). Colors
|
|
334
|
+
* are hex strings here (`'#RRGGBB'`, `'RRGGBB'` or `'AARRGGBB'`), turned
|
|
335
|
+
* into the engine's `{red, green, blue}` objects; alpha is dropped. */
|
|
310
336
|
setPatternFill(sheetIdx: number, data: SelectedData, opts: {
|
|
311
337
|
fgColor?: string;
|
|
312
338
|
bgColor?: string;
|
|
313
339
|
pattern?: StPatternType;
|
|
314
340
|
}): Promise<void>;
|
|
315
|
-
/** Apply borders to the selection per the batch directive.
|
|
341
|
+
/** Apply borders to the selection per the batch directive. The color is
|
|
342
|
+
* 8-hex ARGB (a leading `#` is stripped), see {@link BorderBatchUpdate}. */
|
|
316
343
|
setBorder(sheetIdx: number, data: SelectedData, update: BorderBatchUpdate): Promise<void>;
|
|
317
344
|
/** Apply a generated payload list, skipping the round-trip when empty. */
|
|
318
345
|
private applyGenerated;
|
|
@@ -708,6 +735,11 @@ export declare class WorkbookOps {
|
|
|
708
735
|
* Value. Parks the formula in a throwaway ephemeral cell, reads the result
|
|
709
736
|
* back, and leaves no committed change — the same mechanism the browser
|
|
710
737
|
* uses for shadow cells, here for one-shot evaluation.
|
|
738
|
+
*
|
|
739
|
+
* Each call takes a NEW ephemeral id (from 1, per WorkbookOps instance)
|
|
740
|
+
* and never removes the cell, so the cells accumulate, and keep
|
|
741
|
+
* recomputing, for the life of the loaded workbook. A rejected write is not
|
|
742
|
+
* detected: the read-back then yields whatever the slot holds.
|
|
711
743
|
*/
|
|
712
744
|
evalFormula(sheetIdx: number, formula: string): Promise<Value>;
|
|
713
745
|
/**
|
package/dist/ops/index.js
CHANGED
|
@@ -146,6 +146,23 @@ function toSchemaFieldSpec(f) {
|
|
|
146
146
|
/**
|
|
147
147
|
* High-level workbook operations bound to one engine {@link Client}.
|
|
148
148
|
* Construct one per workbook and share it across the host.
|
|
149
|
+
*
|
|
150
|
+
* Indexes are 0-based; `sheetIdx` is the tab position. Block-relative
|
|
151
|
+
* `row`/`col` count from the block's master (top-left) cell.
|
|
152
|
+
*
|
|
153
|
+
* Failure contract, read before relying on a write:
|
|
154
|
+
* - An operation THROWS (rejects) when the engine answers a call with an
|
|
155
|
+
* `ErrorMessage`, and when this layer refuses its inputs (documented per
|
|
156
|
+
* method).
|
|
157
|
+
* - An operation does NOT throw when the engine refuses the edit itself.
|
|
158
|
+
* The engine reports that as an `ActionEffect` with
|
|
159
|
+
* `status.type === 'err'` (reason in `errorMessage`) and applies nothing.
|
|
160
|
+
* Methods returning `Promise<ActionEffect>` hand that effect back: check
|
|
161
|
+
* `status`. Methods returning `Promise<void>` or a summary DROP it, so a
|
|
162
|
+
* refused transaction looks like success there.
|
|
163
|
+
*
|
|
164
|
+
* Every transaction is built at the injected {@link TempModeProvider}'s
|
|
165
|
+
* current value, so in the browser's temp mode these writes are speculative.
|
|
149
166
|
*/
|
|
150
167
|
export class WorkbookOps {
|
|
151
168
|
constructor(client, tempMode = () => false) {
|
|
@@ -157,7 +174,9 @@ export class WorkbookOps {
|
|
|
157
174
|
/**
|
|
158
175
|
* Build a transaction at the host's current temp-mode, send it, and return
|
|
159
176
|
* the engine's effect. Throws on an engine ErrorMessage so callers can use
|
|
160
|
-
* normal try/catch instead of inspecting a union.
|
|
177
|
+
* normal try/catch instead of inspecting a union. An engine REJECTION is
|
|
178
|
+
* not an ErrorMessage: it comes back as the effect with `status.type ===
|
|
179
|
+
* 'err'` and is returned, not thrown.
|
|
161
180
|
*/
|
|
162
181
|
async apply(payloads, undoable) {
|
|
163
182
|
const transaction = makeTransaction(payloads, undoable, this.tempMode());
|
|
@@ -226,7 +245,11 @@ export class WorkbookOps {
|
|
|
226
245
|
deleteSheet(idx) {
|
|
227
246
|
return this.apply([{ type: 'deleteSheet', value: { idx } }], true);
|
|
228
247
|
}
|
|
229
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* Set a sheet tab's color: 8-hex ARGB with no `#` (e.g. `'FF4472C4'`),
|
|
250
|
+
* or `''` to clear. Stored verbatim and written to the file's `tabColor`;
|
|
251
|
+
* nothing validates or normalises it.
|
|
252
|
+
*/
|
|
230
253
|
setSheetColor(idx, color) {
|
|
231
254
|
return this.apply([{ type: 'setSheetColor', value: { idx, color } }], true);
|
|
232
255
|
}
|
|
@@ -313,7 +336,8 @@ export class WorkbookOps {
|
|
|
313
336
|
// Each method turns the current sheet + selection into style-update
|
|
314
337
|
// payloads (logic in ../format) and applies them. The host supplies the
|
|
315
338
|
// sheet index (a view concern) and the selection.
|
|
316
|
-
/** Apply font styling (bold/italic/underline/strike/color/size).
|
|
339
|
+
/** Apply font styling (bold/italic/underline/strike/color/size). The
|
|
340
|
+
* color is 8-hex ARGB with no `#`, see {@link FontStyle}. */
|
|
317
341
|
async setFont(sheetIdx, data, update) {
|
|
318
342
|
await this.applyGenerated(generateFontPayload(sheetIdx, data, update));
|
|
319
343
|
}
|
|
@@ -329,11 +353,14 @@ export class WorkbookOps {
|
|
|
329
353
|
async setNumFmt(sheetIdx, data, numFmt) {
|
|
330
354
|
await this.applyGenerated(generateNumFmtPayload(sheetIdx, data, numFmt));
|
|
331
355
|
}
|
|
332
|
-
/** Apply a pattern fill (foreground/background color + pattern).
|
|
356
|
+
/** Apply a pattern fill (foreground/background color + pattern). Colors
|
|
357
|
+
* are hex strings here (`'#RRGGBB'`, `'RRGGBB'` or `'AARRGGBB'`), turned
|
|
358
|
+
* into the engine's `{red, green, blue}` objects; alpha is dropped. */
|
|
333
359
|
async setPatternFill(sheetIdx, data, opts) {
|
|
334
360
|
await this.applyGenerated(generatePatternFillPayload(sheetIdx, data, opts.fgColor, opts.bgColor, opts.pattern));
|
|
335
361
|
}
|
|
336
|
-
/** Apply borders to the selection per the batch directive.
|
|
362
|
+
/** Apply borders to the selection per the batch directive. The color is
|
|
363
|
+
* 8-hex ARGB (a leading `#` is stripped), see {@link BorderBatchUpdate}. */
|
|
337
364
|
async setBorder(sheetIdx, data, update) {
|
|
338
365
|
await this.applyGenerated(generateBorderPayloads(sheetIdx, data, update));
|
|
339
366
|
}
|
|
@@ -1367,6 +1394,11 @@ export class WorkbookOps {
|
|
|
1367
1394
|
* Value. Parks the formula in a throwaway ephemeral cell, reads the result
|
|
1368
1395
|
* back, and leaves no committed change — the same mechanism the browser
|
|
1369
1396
|
* uses for shadow cells, here for one-shot evaluation.
|
|
1397
|
+
*
|
|
1398
|
+
* Each call takes a NEW ephemeral id (from 1, per WorkbookOps instance)
|
|
1399
|
+
* and never removes the cell, so the cells accumulate, and keep
|
|
1400
|
+
* recomputing, for the life of the loaded workbook. A rejected write is not
|
|
1401
|
+
* detected: the read-back then yields whatever the slot holds.
|
|
1370
1402
|
*/
|
|
1371
1403
|
async evalFormula(sheetIdx, formula) {
|
|
1372
1404
|
const id = this.ephemeralSeq++;
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped caller identities. The user and each craft get a random uuid
|
|
3
|
+
* on first ask; a host tags each write with it and later maps it back (via
|
|
4
|
+
* {@link resolveActor}) to the actor the engine's `mayModifyBlock` expects.
|
|
5
|
+
* Nothing here persists: uuids and owner records are gone on reload, which
|
|
6
|
+
* is why block ownership that must survive lives on the engine's block.
|
|
7
|
+
*/
|
|
1
8
|
declare class CallerRegistry {
|
|
2
9
|
private _entries;
|
|
3
10
|
private _blockOwners;
|
|
4
11
|
private _fieldPositions;
|
|
12
|
+
/** The user's uuid for this session. */
|
|
5
13
|
getUserUuid(): string;
|
|
14
|
+
/** A craft's uuid for this session. Throws for the reserved user key. */
|
|
6
15
|
getCraftUuid(craftId: string): string;
|
|
7
16
|
isUser(uuid: string | undefined): boolean;
|
|
8
17
|
/**
|
|
@@ -21,6 +30,8 @@ declare class CallerRegistry {
|
|
|
21
30
|
type: 'craft';
|
|
22
31
|
craftId: string;
|
|
23
32
|
} | undefined;
|
|
33
|
+
/** Record which caller created a block. In-memory only; keyed by
|
|
34
|
+
* `sheetIdx`, so it goes stale when sheets move. */
|
|
24
35
|
registerBlockOwner(sheetIdx: number, blockId: number, callerUuid: string): void;
|
|
25
36
|
getBlockOwner(sheetIdx: number, blockId: number): string | undefined;
|
|
26
37
|
/**
|
|
@@ -32,5 +43,6 @@ declare class CallerRegistry {
|
|
|
32
43
|
getFieldRenderId(sheetIdx: number, blockId: number, blockRow: number, blockCol: number): string | undefined;
|
|
33
44
|
private _getOrAssign;
|
|
34
45
|
}
|
|
46
|
+
/** The process-wide registry. */
|
|
35
47
|
export declare const callerRegistry: CallerRegistry;
|
|
36
48
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Permissions — caller identity registry
|
|
1
|
+
// Permissions — the caller identity registry.
|
|
2
2
|
//
|
|
3
3
|
// The CallerRegistry is plain in-process state (no engine, no UI): it maps
|
|
4
4
|
// caller identities to uuids, blocks to owners, and block-relative field
|
|
@@ -11,19 +11,28 @@
|
|
|
11
11
|
// UI/runtime glue, not portable logic.
|
|
12
12
|
import { simpleUuid } from '../utils/index.js';
|
|
13
13
|
const USER_KEY = '__user__';
|
|
14
|
+
/**
|
|
15
|
+
* Session-scoped caller identities. The user and each craft get a random uuid
|
|
16
|
+
* on first ask; a host tags each write with it and later maps it back (via
|
|
17
|
+
* {@link resolveActor}) to the actor the engine's `mayModifyBlock` expects.
|
|
18
|
+
* Nothing here persists: uuids and owner records are gone on reload, which
|
|
19
|
+
* is why block ownership that must survive lives on the engine's block.
|
|
20
|
+
*/
|
|
14
21
|
class CallerRegistry {
|
|
15
22
|
constructor() {
|
|
16
23
|
this._entries = new Map();
|
|
17
24
|
this._blockOwners = new Map();
|
|
18
|
-
// (sheetIdx, blockId, block-relative
|
|
19
|
-
// when patch.ts observes a bindFormSchema payload.
|
|
20
|
-
//
|
|
21
|
-
//
|
|
25
|
+
// (sheetIdx, blockId, axis, block-relative offset) → field renderId.
|
|
26
|
+
// Populated when the app's patch.ts observes a bindFormSchema payload.
|
|
27
|
+
// It used to back the host-side `userEditable` check; that rule now lives
|
|
28
|
+
// on the engine schema, and nothing outside tests reads this map.
|
|
22
29
|
this._fieldPositions = new Map();
|
|
23
30
|
}
|
|
31
|
+
/** The user's uuid for this session. */
|
|
24
32
|
getUserUuid() {
|
|
25
33
|
return this._getOrAssign(USER_KEY);
|
|
26
34
|
}
|
|
35
|
+
/** A craft's uuid for this session. Throws for the reserved user key. */
|
|
27
36
|
getCraftUuid(craftId) {
|
|
28
37
|
if (craftId === USER_KEY) {
|
|
29
38
|
throw new Error(`invalid craftId: ${craftId}`);
|
|
@@ -55,6 +64,8 @@ class CallerRegistry {
|
|
|
55
64
|
}
|
|
56
65
|
return undefined;
|
|
57
66
|
}
|
|
67
|
+
/** Record which caller created a block. In-memory only; keyed by
|
|
68
|
+
* `sheetIdx`, so it goes stale when sheets move. */
|
|
58
69
|
registerBlockOwner(sheetIdx, blockId, callerUuid) {
|
|
59
70
|
this._blockOwners.set(`${sheetIdx}-${blockId}`, callerUuid);
|
|
60
71
|
}
|
|
@@ -84,6 +95,7 @@ class CallerRegistry {
|
|
|
84
95
|
return uuid;
|
|
85
96
|
}
|
|
86
97
|
}
|
|
98
|
+
/** The process-wide registry. */
|
|
87
99
|
export const callerRegistry = new CallerRegistry();
|
|
88
100
|
// `FieldEditableInfo` and `isFieldUserEditable` used to live here, reading a tri-state `userEditable`
|
|
89
101
|
// boolean off the host's own field store. The field's write policy is declared
|
package/dist/port.js
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
//
|
|
8
8
|
// The concrete Client is INJECTED by the host:
|
|
9
9
|
// - browser app -> logisheets-engine's worker-backed WorkbookClient
|
|
10
|
-
// - node runtime -> logisheets'
|
|
10
|
+
// - node runtime -> logisheets-runtime's Proxy over the Node WASM handle(),
|
|
11
|
+
// with each synchronous result wrapped in a Promise
|
|
11
12
|
//
|
|
12
|
-
// Both implement the same `Client` interface
|
|
13
|
-
//
|
|
13
|
+
// Both implement the same `Client` interface, so logisheets-core never needs
|
|
14
|
+
// to know which one it got. (The Node proxy leaves the `register*` callback
|
|
15
|
+
// members unimplemented; core code must not depend on them.)
|
|
14
16
|
export {};
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
/** Whether `value` is a cell (`B3`) or range (`A1:C9`) reference,
|
|
2
|
+
* case-insensitive. No `$`, no sheet prefix. */
|
|
1
3
|
export declare function isA1notation(value: unknown): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Parse a cell or range reference into 0-based indexes: `cs`/`rs` are the
|
|
6
|
+
* start column/row, `ce`/`re` the end (present for a range only).
|
|
7
|
+
* `undefined` when the text is not in {@link isA1notation}'s form.
|
|
8
|
+
*/
|
|
2
9
|
export declare function parseA1notation(value: string): {
|
|
3
10
|
cs: number;
|
|
4
11
|
rs: number;
|
package/dist/utils/a1notation.js
CHANGED
|
@@ -3,12 +3,18 @@ import { upperCase } from '../strings/index.js';
|
|
|
3
3
|
import { isString } from '../type-guard/index.js';
|
|
4
4
|
const a1notationReg = /^(?<cs>[A-Z]+)(?<rs>\d+)(:(?<ce>[A-Z]+)(?<re>\d+))?$/i;
|
|
5
5
|
const notationReq = /^[A-Z]/;
|
|
6
|
+
/** Whether `value` is a cell (`B3`) or range (`A1:C9`) reference,
|
|
7
|
+
* case-insensitive. No `$`, no sheet prefix. */
|
|
6
8
|
export function isA1notation(value) {
|
|
7
9
|
if (!isString(value))
|
|
8
10
|
return false;
|
|
9
11
|
return a1notationReg.test(upperCase(value));
|
|
10
12
|
}
|
|
11
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Parse a cell or range reference into 0-based indexes: `cs`/`rs` are the
|
|
15
|
+
* start column/row, `ce`/`re` the end (present for a range only).
|
|
16
|
+
* `undefined` when the text is not in {@link isA1notation}'s form.
|
|
17
|
+
*/
|
|
12
18
|
export function parseA1notation(value) {
|
|
13
19
|
const result = upperCase(value).match(a1notationReg);
|
|
14
20
|
if (!result?.groups)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logisheets-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
4
4
|
"description": "UI-free LogiSheets logic — runs in the browser or on Node. Depends on logisheets-web only for types; the engine Client is injected by the host.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"homepage": "https://github.com/logisky/LogiSheets",
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"logisheets-web": "^1.
|
|
66
|
+
"logisheets-web": "^1.16.1"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/node": "^18",
|