logisheets-core 1.7.1 → 1.9.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/README.md +72 -0
- package/dist/format/index.d.ts +2 -0
- package/dist/format/index.js +8 -0
- package/dist/ops/index.d.ts +51 -0
- package/dist/ops/index.js +115 -0
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# logisheets-core
|
|
2
|
+
|
|
3
|
+
UI-free LogiSheets logic — the portable core shared by every host. It holds all
|
|
4
|
+
the high-level workbook operations (styling, blocks, fields, validation, crafts,
|
|
5
|
+
transactions, permissions) with **no rendering and no runtime of its own**, so
|
|
6
|
+
the exact same code runs in the browser app and in a headless Node runtime.
|
|
7
|
+
|
|
8
|
+
It depends on [`logisheets-web`](https://www.npmjs.com/package/logisheets-web)
|
|
9
|
+
for **types only** — the concrete engine `Client` is injected by the host:
|
|
10
|
+
|
|
11
|
+
- the **browser app** injects the worker-backed client from `logisheets-engine`;
|
|
12
|
+
- [`logisheets-runtime`](https://www.npmjs.com/package/logisheets-runtime)
|
|
13
|
+
injects a synchronous client built on the Node WASM engine.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install logisheets-core logisheets-web
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`logisheets-web` is a peer dependency (used for types). This package is
|
|
22
|
+
ESM-only.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
You rarely construct the engine `Client` yourself — a host normally provides it.
|
|
27
|
+
Given a `Client`, `WorkbookOps` is the high-level operation layer:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import {WorkbookOps} from 'logisheets-core'
|
|
31
|
+
import type {Client} from 'logisheets-web'
|
|
32
|
+
|
|
33
|
+
declare const client: Client // supplied by the host (browser worker or Node)
|
|
34
|
+
|
|
35
|
+
const ops = new WorkbookOps(client)
|
|
36
|
+
|
|
37
|
+
await ops.inputCell(0, 0, 0, 'Hello') // sheet 0, cell A1
|
|
38
|
+
await ops.createSheet(1, 'Summary')
|
|
39
|
+
await ops.setSheetColor(0, '#4472C4')
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`WorkbookOps` covers cell input (including block cells and cell images), sheet
|
|
43
|
+
management (create / delete / rename / color), block operations (move, remove,
|
|
44
|
+
insert rows), and style payload generation (borders, fills, alignment, wrap).
|
|
45
|
+
|
|
46
|
+
## Subpath exports
|
|
47
|
+
|
|
48
|
+
Focused utilities are available without pulling in the whole surface:
|
|
49
|
+
|
|
50
|
+
| Import | Contents |
|
|
51
|
+
|--------|----------|
|
|
52
|
+
| `logisheets-core` | Full surface: ops, format, crafts, validation, fields, values, transactions, permissions |
|
|
53
|
+
| `logisheets-core/strings` | String helpers |
|
|
54
|
+
| `logisheets-core/type-guard` | Runtime type guards for engine types |
|
|
55
|
+
| `logisheets-core/selection` | Selection helpers |
|
|
56
|
+
| `logisheets-core/utils` | General utilities |
|
|
57
|
+
| `logisheets-core/value` | Cell-value helpers |
|
|
58
|
+
|
|
59
|
+
## Where it fits
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
logisheets-web / logisheets (WASM engine, per host)
|
|
63
|
+
│ Client (types only)
|
|
64
|
+
▼
|
|
65
|
+
logisheets-core ← you are here (UI-free logic)
|
|
66
|
+
┌───────┴────────┐
|
|
67
|
+
Browser App logisheets-runtime (Node)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT — part of the [LogiSheets](https://github.com/logisky/LogiSheets) project.
|
package/dist/format/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface FontStyle {
|
|
|
6
6
|
color?: string;
|
|
7
7
|
size?: number;
|
|
8
8
|
strike?: boolean;
|
|
9
|
+
/** Font family name, e.g. "Arial", "Times New Roman", "微软雅黑". */
|
|
10
|
+
name?: string;
|
|
9
11
|
}
|
|
10
12
|
export declare function generateFontPayload(sheetIdx: number, data: SelectedData, update: FontStyle): readonly Payload[];
|
|
11
13
|
export declare function generateAlgnmentPayload(sheetIdx: number, data: SelectedData, alignment: Alignment): readonly Payload[];
|
package/dist/format/index.js
CHANGED
|
@@ -52,6 +52,8 @@ export function generateFontPayload(sheetIdx, data, update) {
|
|
|
52
52
|
cellTy.setFontSize = update.size;
|
|
53
53
|
if (update.strike !== undefined)
|
|
54
54
|
cellTy.setFontStrike = update.strike;
|
|
55
|
+
if (update.name)
|
|
56
|
+
cellTy.setFontName = update.name;
|
|
55
57
|
const d = data.data.d;
|
|
56
58
|
const result = [];
|
|
57
59
|
for (let i = d.startRow; i <= d.endRow; i += 1)
|
|
@@ -69,6 +71,12 @@ export function generateFontPayload(sheetIdx, data, update) {
|
|
|
69
71
|
lineTy.setFontItalic = update.italic;
|
|
70
72
|
if (update.color)
|
|
71
73
|
lineTy.setFontColor = update.color;
|
|
74
|
+
if (update.size)
|
|
75
|
+
lineTy.setFontSize = update.size;
|
|
76
|
+
if (update.strike !== undefined)
|
|
77
|
+
lineTy.setFontStrike = update.strike;
|
|
78
|
+
if (update.name)
|
|
79
|
+
lineTy.setFontName = update.name;
|
|
72
80
|
return [lineStyle(sheetIdx, d.start, d.end, d.type === 'row', lineTy)];
|
|
73
81
|
}
|
|
74
82
|
export function generateAlgnmentPayload(sheetIdx, data, alignment) {
|
package/dist/ops/index.d.ts
CHANGED
|
@@ -83,6 +83,28 @@ export declare class WorkbookOps {
|
|
|
83
83
|
* clear (see the drag-to-move overlay, which cancels on collision).
|
|
84
84
|
*/
|
|
85
85
|
moveBlock(sheetIdx: number, blockId: number, newMasterRow: number, newMasterCol: number): Promise<ActionEffect>;
|
|
86
|
+
/**
|
|
87
|
+
* Sort a block's records by the named field. Rows are reordered for a
|
|
88
|
+
* row-schema block, columns for a col-schema block. The engine computes a
|
|
89
|
+
* type-aware order (numbers numerically, text lexicographically, blanks
|
|
90
|
+
* last); the reorder is one undoable transaction.
|
|
91
|
+
*
|
|
92
|
+
* Throws for random-schema blocks (no fields) or an unknown field name.
|
|
93
|
+
*/
|
|
94
|
+
sortBlock(sheetIdx: number, blockId: number, field: string, asc?: boolean, undoable?: boolean): Promise<ActionEffect>;
|
|
95
|
+
/**
|
|
96
|
+
* Move a single block line (column or row) from one position to another —
|
|
97
|
+
* positional `remove(from)` then `insert(to)` on the block's line order
|
|
98
|
+
* (`MoveBlockLine`). For a row-schema form block, a FIELD is a column, so
|
|
99
|
+
* reordering fields is `isRow = false` with block-inner column indices.
|
|
100
|
+
*
|
|
101
|
+
* `to` is the index in the post-removal frame (matching the engine's
|
|
102
|
+
* `move_line`). The field's stable line id keeps its cell data, and the
|
|
103
|
+
* schema's per-field `idx` is re-derived from the new position on the next
|
|
104
|
+
* read — so both data and field order follow the move automatically; no
|
|
105
|
+
* re-`bindFormSchema` is needed.
|
|
106
|
+
*/
|
|
107
|
+
moveBlockLine(sheetIdx: number, blockId: number, from: number, to: number, isRow?: boolean, undoable?: boolean): Promise<ActionEffect>;
|
|
86
108
|
/** Apply font styling (bold/italic/underline/strike/color/size). */
|
|
87
109
|
setFont(sheetIdx: number, data: SelectedData, update: FontStyle): Promise<void>;
|
|
88
110
|
/** Apply horizontal/vertical alignment. */
|
|
@@ -137,6 +159,35 @@ export declare class WorkbookOps {
|
|
|
137
159
|
keyIdx: number;
|
|
138
160
|
fields: readonly FormBlockField[];
|
|
139
161
|
}): Promise<void>;
|
|
162
|
+
/**
|
|
163
|
+
* Edit an EXISTING form-backed block: rename it, re-type / re-formula its
|
|
164
|
+
* existing fields, and/or append new fields — in one transaction. The v1
|
|
165
|
+
* contract is **fields are never removed**, so the column count is
|
|
166
|
+
* monotonically non-decreasing: `fields.length >= currentColCnt`. That
|
|
167
|
+
* keeps this safe with only a *tail* `resizeBlock` (new columns are
|
|
168
|
+
* appended, existing field columns — and the cells/formulas that
|
|
169
|
+
* reference them — are untouched, and no schema entry is ever orphaned).
|
|
170
|
+
*
|
|
171
|
+
* `fields` is the FULL field list (existing fields first, in their
|
|
172
|
+
* current order, followed by any newly-added fields). Existing fields
|
|
173
|
+
* MUST keep their original `renderId` so the block's cells stay wired to
|
|
174
|
+
* their render/type metadata; the host reconstructs the list from
|
|
175
|
+
* `BlockInfo.schema.fields[i].renderId`.
|
|
176
|
+
*
|
|
177
|
+
* Order matters: the `resizeBlock` runs BEFORE `bindFormSchema` so the
|
|
178
|
+
* appended field columns exist when the schema binds to them. Validation
|
|
179
|
+
* / editability formulas are carried on `FieldInfo` (the field type) via
|
|
180
|
+
* the host's FieldManager, same as `createFormBlock`, so this only needs
|
|
181
|
+
* to round-trip each field's value-formula template.
|
|
182
|
+
*/
|
|
183
|
+
editFormBlock(opts: {
|
|
184
|
+
sheetIdx: number;
|
|
185
|
+
blockId: number;
|
|
186
|
+
currentColCnt: number;
|
|
187
|
+
refName: string;
|
|
188
|
+
keyIdx: number;
|
|
189
|
+
fields: readonly FormBlockField[];
|
|
190
|
+
}): Promise<void>;
|
|
140
191
|
/**
|
|
141
192
|
* Apply a caller-built payload list as one transaction (at the host's
|
|
142
193
|
* temp-mode). Escape hatch for operations whose payload construction still
|
package/dist/ops/index.js
CHANGED
|
@@ -140,6 +140,56 @@ export class WorkbookOps {
|
|
|
140
140
|
},
|
|
141
141
|
], true);
|
|
142
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Sort a block's records by the named field. Rows are reordered for a
|
|
145
|
+
* row-schema block, columns for a col-schema block. The engine computes a
|
|
146
|
+
* type-aware order (numbers numerically, text lexicographically, blanks
|
|
147
|
+
* last); the reorder is one undoable transaction.
|
|
148
|
+
*
|
|
149
|
+
* Throws for random-schema blocks (no fields) or an unknown field name.
|
|
150
|
+
*/
|
|
151
|
+
async sortBlock(sheetIdx, blockId, field, asc = true, undoable = true) {
|
|
152
|
+
const order = await this.client.getBlockSortOrder({
|
|
153
|
+
sheetIdx,
|
|
154
|
+
blockId,
|
|
155
|
+
field,
|
|
156
|
+
asc,
|
|
157
|
+
});
|
|
158
|
+
if (isErrorMessage(order)) {
|
|
159
|
+
throw new Error('Sort failed: ' + order.msg);
|
|
160
|
+
}
|
|
161
|
+
return this.apply([
|
|
162
|
+
{
|
|
163
|
+
type: 'reorderBlockLines',
|
|
164
|
+
value: {
|
|
165
|
+
sheetIdx,
|
|
166
|
+
blockId,
|
|
167
|
+
isRow: order.isRow,
|
|
168
|
+
newOrder: order.newOrder,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
], undoable);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Move a single block line (column or row) from one position to another —
|
|
175
|
+
* positional `remove(from)` then `insert(to)` on the block's line order
|
|
176
|
+
* (`MoveBlockLine`). For a row-schema form block, a FIELD is a column, so
|
|
177
|
+
* reordering fields is `isRow = false` with block-inner column indices.
|
|
178
|
+
*
|
|
179
|
+
* `to` is the index in the post-removal frame (matching the engine's
|
|
180
|
+
* `move_line`). The field's stable line id keeps its cell data, and the
|
|
181
|
+
* schema's per-field `idx` is re-derived from the new position on the next
|
|
182
|
+
* read — so both data and field order follow the move automatically; no
|
|
183
|
+
* re-`bindFormSchema` is needed.
|
|
184
|
+
*/
|
|
185
|
+
moveBlockLine(sheetIdx, blockId, from, to, isRow = false, undoable = true) {
|
|
186
|
+
return this.apply([
|
|
187
|
+
{
|
|
188
|
+
type: 'moveBlockLine',
|
|
189
|
+
value: { sheetIdx, blockId, from, to, isRow },
|
|
190
|
+
},
|
|
191
|
+
], undoable);
|
|
192
|
+
}
|
|
143
193
|
// ---- formatting -----------------------------------------------------
|
|
144
194
|
//
|
|
145
195
|
// Each method turns the current sheet + selection into style-update
|
|
@@ -274,6 +324,71 @@ export class WorkbookOps {
|
|
|
274
324
|
];
|
|
275
325
|
await this.apply(payloads, true);
|
|
276
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Edit an EXISTING form-backed block: rename it, re-type / re-formula its
|
|
329
|
+
* existing fields, and/or append new fields — in one transaction. The v1
|
|
330
|
+
* contract is **fields are never removed**, so the column count is
|
|
331
|
+
* monotonically non-decreasing: `fields.length >= currentColCnt`. That
|
|
332
|
+
* keeps this safe with only a *tail* `resizeBlock` (new columns are
|
|
333
|
+
* appended, existing field columns — and the cells/formulas that
|
|
334
|
+
* reference them — are untouched, and no schema entry is ever orphaned).
|
|
335
|
+
*
|
|
336
|
+
* `fields` is the FULL field list (existing fields first, in their
|
|
337
|
+
* current order, followed by any newly-added fields). Existing fields
|
|
338
|
+
* MUST keep their original `renderId` so the block's cells stay wired to
|
|
339
|
+
* their render/type metadata; the host reconstructs the list from
|
|
340
|
+
* `BlockInfo.schema.fields[i].renderId`.
|
|
341
|
+
*
|
|
342
|
+
* Order matters: the `resizeBlock` runs BEFORE `bindFormSchema` so the
|
|
343
|
+
* appended field columns exist when the schema binds to them. Validation
|
|
344
|
+
* / editability formulas are carried on `FieldInfo` (the field type) via
|
|
345
|
+
* the host's FieldManager, same as `createFormBlock`, so this only needs
|
|
346
|
+
* to round-trip each field's value-formula template.
|
|
347
|
+
*/
|
|
348
|
+
async editFormBlock(opts) {
|
|
349
|
+
const { sheetIdx, blockId, currentColCnt, refName, keyIdx, fields } = opts;
|
|
350
|
+
const newColCnt = fields.length;
|
|
351
|
+
if (newColCnt < currentColCnt) {
|
|
352
|
+
throw new Error(`editFormBlock cannot remove fields: got ${newColCnt} field(s) ` +
|
|
353
|
+
`for a block with ${currentColCnt} column(s).`);
|
|
354
|
+
}
|
|
355
|
+
const payloads = [];
|
|
356
|
+
if (newColCnt !== currentColCnt) {
|
|
357
|
+
payloads.push({
|
|
358
|
+
type: 'resizeBlock',
|
|
359
|
+
value: {
|
|
360
|
+
sheetIdx,
|
|
361
|
+
id: blockId,
|
|
362
|
+
newColCnt,
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
payloads.push({
|
|
367
|
+
type: 'bindFormSchema',
|
|
368
|
+
value: {
|
|
369
|
+
refName,
|
|
370
|
+
sheetIdx,
|
|
371
|
+
blockId,
|
|
372
|
+
fieldFrom: 0,
|
|
373
|
+
row: true,
|
|
374
|
+
keyIdx: keyIdx < 0 ? 0 : keyIdx,
|
|
375
|
+
fields: fields.map((f) => f.name),
|
|
376
|
+
renderIds: fields.map((f) => f.renderId),
|
|
377
|
+
fieldFormulas: fields.map((f) => f.valueFormula ?? ''),
|
|
378
|
+
validationFormulas: [],
|
|
379
|
+
editabilityFormulas: [],
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
payloads.push(...fields.map((f) => ({
|
|
383
|
+
type: 'upsertFieldRenderInfo',
|
|
384
|
+
value: {
|
|
385
|
+
renderId: f.renderId,
|
|
386
|
+
diyRender: f.diyRender,
|
|
387
|
+
styleUpdate: { setNumFmt: f.numFmt ?? '' },
|
|
388
|
+
},
|
|
389
|
+
})));
|
|
390
|
+
await this.apply(payloads, true);
|
|
391
|
+
}
|
|
277
392
|
// ---- generic / temp-branch -----------------------------------------
|
|
278
393
|
/**
|
|
279
394
|
* Apply a caller-built payload list as one transaction (at the host's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logisheets-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
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",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"author": "Jeremy He",
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"logisheets-web": "^1.
|
|
47
|
+
"logisheets-web": "^1.9.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"logisheets-web": "workspace:*",
|
|
51
51
|
"typescript": "^5.5.0",
|
|
52
|
-
"vitest": "
|
|
52
|
+
"vitest": "3.2.6"
|
|
53
53
|
}
|
|
54
54
|
}
|