logisheets-core 1.13.0 → 1.14.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.
@@ -17,6 +17,16 @@ export interface FormBlockField {
17
17
  renderId: string;
18
18
  /** Per-field value-formula template (#FIELD("X") / #KEY); '' if free-form. */
19
19
  valueFormula?: string;
20
+ /**
21
+ * Per-field validation template (#PLACEHOLDER for the value under test,
22
+ * #FIELD("X") for a same-row sibling); '' when the field has no rule.
23
+ *
24
+ * This goes into the schema rather than staying host-side so the engine
25
+ * installs the per-record shadow itself — on bind AND on every row added
26
+ * later — and so one answer serves every reader: the warning marker, the
27
+ * `overrideValidation` write gate, and any other host.
28
+ */
29
+ validationFormula?: string;
20
30
  /** Whether the field renders via a host-drawn (DIY) overlay. */
21
31
  diyRender: boolean;
22
32
  /** Number format applied to the field's render info. */
@@ -188,6 +198,23 @@ export declare class WorkbookOps {
188
198
  keyIdx: number;
189
199
  fields: readonly FormBlockField[];
190
200
  }): Promise<void>;
201
+ /**
202
+ * Rewrite ONE kind of per-field rule on a block, leaving the others alone.
203
+ *
204
+ * `formulas` is one entry per field, in the schema's field order — a rule
205
+ * or `''` for none. The other two rule kinds are sent empty, which the
206
+ * engine reads as "don't touch these", so editing a validation rule cannot
207
+ * clear the value formulas standing next to it.
208
+ *
209
+ * Every existing row is re-materialized from the new rule, so this is also
210
+ * how a rule is removed: pass `''` for that field.
211
+ */
212
+ setFieldRules(opts: {
213
+ sheetIdx: number;
214
+ blockId: number;
215
+ kind: 'value' | 'validation' | 'editability';
216
+ formulas: readonly string[];
217
+ }): Promise<void>;
191
218
  /**
192
219
  * Apply a caller-built payload list as one transaction (at the host's
193
220
  * temp-mode). Escape hatch for operations whose payload construction still
package/dist/ops/index.js CHANGED
@@ -262,7 +262,7 @@ export class WorkbookOps {
262
262
  fields: fields.map((f) => f.name),
263
263
  renderIds: fields.map((f) => f.renderId),
264
264
  fieldFormulas: fields.map((f) => f.valueFormula ?? ''),
265
- validationFormulas: [],
265
+ validationFormulas: fields.map((f) => f.validationFormula ?? ''),
266
266
  editabilityFormulas: [],
267
267
  },
268
268
  },
@@ -309,7 +309,7 @@ export class WorkbookOps {
309
309
  fields: fields.map((f) => f.name),
310
310
  renderIds: fields.map((f) => f.renderId),
311
311
  fieldFormulas: fields.map((f) => f.valueFormula ?? ''),
312
- validationFormulas: [],
312
+ validationFormulas: fields.map((f) => f.validationFormula ?? ''),
313
313
  editabilityFormulas: [],
314
314
  },
315
315
  },
@@ -375,7 +375,7 @@ export class WorkbookOps {
375
375
  fields: fields.map((f) => f.name),
376
376
  renderIds: fields.map((f) => f.renderId),
377
377
  fieldFormulas: fields.map((f) => f.valueFormula ?? ''),
378
- validationFormulas: [],
378
+ validationFormulas: fields.map((f) => f.validationFormula ?? ''),
379
379
  editabilityFormulas: [],
380
380
  },
381
381
  });
@@ -389,6 +389,33 @@ export class WorkbookOps {
389
389
  })));
390
390
  await this.apply(payloads, true);
391
391
  }
392
+ /**
393
+ * Rewrite ONE kind of per-field rule on a block, leaving the others alone.
394
+ *
395
+ * `formulas` is one entry per field, in the schema's field order — a rule
396
+ * or `''` for none. The other two rule kinds are sent empty, which the
397
+ * engine reads as "don't touch these", so editing a validation rule cannot
398
+ * clear the value formulas standing next to it.
399
+ *
400
+ * Every existing row is re-materialized from the new rule, so this is also
401
+ * how a rule is removed: pass `''` for that field.
402
+ */
403
+ async setFieldRules(opts) {
404
+ const { sheetIdx, blockId, kind, formulas } = opts;
405
+ const forKind = (k) => kind === k ? formulas.map((f) => f ?? '') : [];
406
+ await this.apply([
407
+ {
408
+ type: 'upsertFieldFormulas',
409
+ value: {
410
+ sheetIdx,
411
+ blockId,
412
+ fieldFormulas: forKind('value'),
413
+ validationFormulas: forKind('validation'),
414
+ editabilityFormulas: forKind('editability'),
415
+ },
416
+ },
417
+ ], true);
418
+ }
392
419
  // ---- generic / temp-branch -----------------------------------------
393
420
  /**
394
421
  * Apply a caller-built payload list as one transaction (at the host's
@@ -5,6 +5,22 @@ declare class CallerRegistry {
5
5
  getUserUuid(): string;
6
6
  getCraftUuid(craftId: string): string;
7
7
  isUser(uuid: string | undefined): boolean;
8
+ /**
9
+ * The engine-side identity a caller uuid stands for: `'user'`, or the
10
+ * craft named by the id it registered under.
11
+ *
12
+ * The uuids here are session-scoped, while a block's `owner` is a craft id
13
+ * saved in the file — so asking the engine whether a caller may touch a
14
+ * block means translating back first. `undefined` for a uuid this registry
15
+ * never issued, which the caller should treat as unidentified rather than
16
+ * as the user.
17
+ */
18
+ resolveActor(uuid: string | undefined): {
19
+ type: 'user';
20
+ } | {
21
+ type: 'craft';
22
+ craftId: string;
23
+ } | undefined;
8
24
  registerBlockOwner(sheetIdx: number, blockId: number, callerUuid: string): void;
9
25
  getBlockOwner(sheetIdx: number, blockId: number): string | undefined;
10
26
  /**
@@ -33,6 +33,28 @@ class CallerRegistry {
33
33
  isUser(uuid) {
34
34
  return uuid === this._entries.get(USER_KEY);
35
35
  }
36
+ /**
37
+ * The engine-side identity a caller uuid stands for: `'user'`, or the
38
+ * craft named by the id it registered under.
39
+ *
40
+ * The uuids here are session-scoped, while a block's `owner` is a craft id
41
+ * saved in the file — so asking the engine whether a caller may touch a
42
+ * block means translating back first. `undefined` for a uuid this registry
43
+ * never issued, which the caller should treat as unidentified rather than
44
+ * as the user.
45
+ */
46
+ resolveActor(uuid) {
47
+ if (uuid === undefined)
48
+ return undefined;
49
+ for (const [key, value] of this._entries) {
50
+ if (value !== uuid)
51
+ continue;
52
+ return key === USER_KEY
53
+ ? { type: 'user' }
54
+ : { type: 'craft', craftId: key };
55
+ }
56
+ return undefined;
57
+ }
36
58
  registerBlockOwner(sheetIdx, blockId, callerUuid) {
37
59
  this._blockOwners.set(`${sheetIdx}-${blockId}`, callerUuid);
38
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "logisheets-core",
3
- "version": "1.13.0",
3
+ "version": "1.14.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.13.0"
66
+ "logisheets-web": "^1.14.1"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/node": "^18",