@datalayer/cells 0.0.0-stage → 0.0.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -2
  3. package/lib/chrome/index.d.ts +7 -0
  4. package/lib/chrome/index.d.ts.map +1 -0
  5. package/lib/chrome/index.js +7 -0
  6. package/lib/chrome/index.js.map +1 -0
  7. package/lib/collab/index.d.ts +7 -0
  8. package/lib/collab/index.d.ts.map +1 -0
  9. package/lib/collab/index.js +7 -0
  10. package/lib/collab/index.js.map +1 -0
  11. package/lib/core/axis.d.ts +97 -0
  12. package/lib/core/axis.d.ts.map +1 -0
  13. package/lib/core/axis.js +431 -0
  14. package/lib/core/axis.js.map +1 -0
  15. package/lib/core/cells.d.ts +71 -0
  16. package/lib/core/cells.d.ts.map +1 -0
  17. package/lib/core/cells.js +285 -0
  18. package/lib/core/cells.js.map +1 -0
  19. package/lib/core/index.d.ts +8 -0
  20. package/lib/core/index.d.ts.map +1 -0
  21. package/lib/core/index.js +7 -0
  22. package/lib/core/index.js.map +1 -0
  23. package/lib/core/strings.d.ts +19 -0
  24. package/lib/core/strings.d.ts.map +1 -0
  25. package/lib/core/strings.js +61 -0
  26. package/lib/core/strings.js.map +1 -0
  27. package/lib/core/styles.d.ts +48 -0
  28. package/lib/core/styles.d.ts.map +1 -0
  29. package/lib/core/styles.js +102 -0
  30. package/lib/core/styles.js.map +1 -0
  31. package/lib/core/values.d.ts +20 -0
  32. package/lib/core/values.d.ts.map +1 -0
  33. package/lib/core/values.js +39 -0
  34. package/lib/core/values.js.map +1 -0
  35. package/lib/core/zone.d.ts +48 -0
  36. package/lib/core/zone.d.ts.map +1 -0
  37. package/lib/core/zone.js +89 -0
  38. package/lib/core/zone.js.map +1 -0
  39. package/lib/csv/index.d.ts +7 -0
  40. package/lib/csv/index.d.ts.map +1 -0
  41. package/lib/csv/index.js +7 -0
  42. package/lib/csv/index.js.map +1 -0
  43. package/lib/index.d.ts +8 -0
  44. package/lib/index.d.ts.map +1 -0
  45. package/lib/index.js +13 -0
  46. package/lib/index.js.map +1 -0
  47. package/lib/plugin/index.d.ts +7 -0
  48. package/lib/plugin/index.d.ts.map +1 -0
  49. package/lib/plugin/index.js +7 -0
  50. package/lib/plugin/index.js.map +1 -0
  51. package/lib/points/resolve.d.ts +56 -0
  52. package/lib/points/resolve.d.ts.map +1 -0
  53. package/lib/points/resolve.js +106 -0
  54. package/lib/points/resolve.js.map +1 -0
  55. package/lib/xlsx/index.d.ts +7 -0
  56. package/lib/xlsx/index.d.ts.map +1 -0
  57. package/lib/xlsx/index.js +7 -0
  58. package/lib/xlsx/index.js.map +1 -0
  59. package/package.json +115 -4
  60. package/style/cells.css +7 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-2026 Datalayer, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,56 @@
1
- # Temporary Holding Version
1
+ [![Datalayer](https://images.datalayer.io/legacy/datalayer-25.svg)](https://datalayer.ai)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)
4
+
5
+ # ☰ 🦠 Datalayer Cells
6
+
7
+ A spreadsheet drawn on canvas and exposed as a React component, over a
8
+ headless model that also runs in Node and in workers. Cells is built on
9
+ [Reactor](https://github.com/datalayer/reactor): every extension point (formula
10
+ functions, cell types, commands, importers and exporters, data sources, the
11
+ collaboration provider) is a Reactor contribution point, and the built-in
12
+ features are Reactor plugins contributing through the same points.
13
+
14
+ > Work in progress: nothing is published yet.
15
+
16
+ ## Packages
17
+
18
+ | Import | What |
19
+ | ------------------------- | ------------------------------------------------------- |
20
+ | `@datalayer/cells` | the React component and hooks; re-exports `./core` |
21
+ | `@datalayer/cells/core` | the headless model: no React, no DOM |
22
+ | `@datalayer/cells/csv` | CSV and TSV in and out |
23
+ | `@datalayer/cells/xlsx` | XLSX in and out, loaded lazily |
24
+ | `@datalayer/cells/collab` | real-time collaboration over Loro |
25
+ | `@datalayer/cells/chrome` | toolbar, formula bar and sheet tabs |
26
+ | `@datalayer/cells/plugin` | views for a Reactor shell |
27
+
28
+ ## Development
29
+
30
+ The reactor is resolved from a checkout beside this repository (`../reactor`).
31
+
32
+ ```bash
33
+ npm install
34
+ npm run lint # Prettier and the licence header on every file
35
+ npm run typecheck # everything, tests included, against ../reactor/src
36
+ npm test # unit tests (vitest, two workers)
37
+ npm run build # lib/
38
+ npm run bench # Node benches into bench/results/
39
+ npm run size # bundle sizes against their budgets
40
+ ```
41
+
42
+ Browser benches and end-to-end tests run on the system Chrome, headless:
43
+
44
+ ```bash
45
+ env -u WAYLAND_DISPLAY -u DISPLAY node scripts/bench.mjs --browser
46
+ ```
47
+
48
+ ## Credits
49
+
50
+ Cells was designed after reading [x-spreadsheet](https://github.com/myliang/x-spreadsheet)
51
+ (MIT) and [o-spreadsheet](https://github.com/odoo/o-spreadsheet) (LGPLv3) for
52
+ ideas; no code of either is included. See [THIRD_PARTY.md](THIRD_PARTY.md).
53
+
54
+ ## License
55
+
56
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `@datalayer/cells/chrome`: the optional toolbar, formula bar and sheet tabs.
3
+ *
4
+ * @module chrome
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/chrome/index.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AAEH,OAAO,EAAE,CAAC"}
@@ -0,0 +1,7 @@
1
+ /*
2
+ * Copyright (c) 2022-2026 Datalayer, Inc.
3
+ *
4
+ * MIT License
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chrome/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `@datalayer/cells/collab`: real-time collaboration over Loro.
3
+ *
4
+ * @module collab
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collab/index.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AAEH,OAAO,EAAE,CAAC"}
@@ -0,0 +1,7 @@
1
+ /*
2
+ * Copyright (c) 2022-2026 Datalayer, Inc.
3
+ *
4
+ * MIT License
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/collab/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * An axis of a sheet: its rows, or its columns.
3
+ *
4
+ * - **Ids.** Every index has a stable id that follows the row through inserts,
5
+ * deletes and moves. Ids are materialised only up to the last index ever
6
+ * named; beyond, an index has an *implicit* id derived from where the row
7
+ * was when the axis was created (`IMPLICIT_BASE + creation index`), so two
8
+ * replicas that materialise the same index independently agree on its id.
9
+ * Inserted indexes get fresh ids, from `FRESH_BASE` up.
10
+ * - **Sizes.** Default until the first resize; then a raw `Float64Array` of
11
+ * sizes and a Fenwick tree over the effective sizes (0 when hidden), so
12
+ * `offsetOf` and `indexAt` are O(log n) and a resize is O(log n). An insert
13
+ * or delete rebuilds the tree from the edit point only.
14
+ * - **Hidden** is a bitset beside the sizes: a hidden index keeps its size for
15
+ * unhide and contributes 0 to offsets.
16
+ *
17
+ * @module core/axis
18
+ */
19
+ /** Implicit ids are `IMPLICIT_BASE + creation index`. */
20
+ export declare const IMPLICIT_BASE = 0;
21
+ /** Fresh ids (inserted indexes) start here, above any creation index. */
22
+ export declare const FRESH_BASE: number;
23
+ /** The state of a run of indexes, as patches carry it. */
24
+ export interface AxisSegment {
25
+ readonly ids: Int32Array;
26
+ /** `null`: every size is the default. */
27
+ readonly sizes: Float64Array | null;
28
+ /** `null`: none is hidden; else 1 per hidden index. */
29
+ readonly hidden: Uint8Array | null;
30
+ }
31
+ export interface AxisOptions {
32
+ /** The logical number of indexes (1,048,576 rows, 16,384 columns in Excel). */
33
+ readonly count: number;
34
+ /** The size of an index nobody resized, in CSS px. */
35
+ readonly defaultSize: number;
36
+ }
37
+ export declare class Axis {
38
+ #private;
39
+ readonly count: number;
40
+ readonly defaultSize: number;
41
+ constructor(options: AxisOptions);
42
+ /** The id of an index, materialised or implicit. */
43
+ idAt(index: number): number;
44
+ /**
45
+ * Materialise ids up to `index` inclusive and return its id. Anything that
46
+ * names an index (content, a size, a merge corner, a reference) calls this
47
+ * first, so no implicit id is ever referenced.
48
+ */
49
+ materialize(index: number): number;
50
+ /** How many ids are materialised. */
51
+ get materialized(): number;
52
+ /** The index of an id, or -1. O(n) over the materialised ids; for the binding, not the hot path. */
53
+ indexOfId(id: number): number;
54
+ /** A copy of the materialised ids, for snapshots and the file format. */
55
+ ids(): Int32Array;
56
+ /** The state of `n` indexes from `at`: ids, sizes and hidden flags, for patches. */
57
+ segment(at: number, n: number): AxisSegment;
58
+ /**
59
+ * Insert `n` indexes before `at`. The count stays: the last `n` indexes fall
60
+ * off the end (the command checks they are empty), and come back as
61
+ * `dropped`. With `segment`, the inserted indexes take its ids, sizes and
62
+ * hidden flags (undo, redo, remote inserts); without, fresh ids and the
63
+ * default size.
64
+ */
65
+ insert(at: number, n: number, segment?: AxisSegment): {
66
+ inserted: AxisSegment;
67
+ dropped: AxisSegment;
68
+ };
69
+ /**
70
+ * Delete `n` indexes from `at`; returns them. New indexes appear at the end:
71
+ * implicit ones, or `tail`'s when given (undoing an insert brings back the
72
+ * indexes it pushed off).
73
+ */
74
+ delete(at: number, n: number, tail?: AxisSegment): AxisSegment;
75
+ /**
76
+ * Move `n` indexes from `at` to before `to` (an index in the coordinates
77
+ * before the move, outside `[at, at + n]`). Ids, sizes and hidden flags travel.
78
+ */
79
+ move(at: number, n: number, to: number): void;
80
+ /** Set sizes and hidden flags of `n` indexes from `at` at once (patches). */
81
+ setSegmentSizes(at: number, sizes: Float64Array | null, hidden: Uint8Array | null, n: number): void;
82
+ /** The size of an index as set, hidden or not. */
83
+ sizeOf(index: number): number;
84
+ /** The size an index takes on screen: 0 when hidden. */
85
+ effectiveSizeOf(index: number): number;
86
+ /** Set the size of an index. O(log n). */
87
+ resize(index: number, size: number): void;
88
+ isHidden(index: number): boolean;
89
+ setHidden(index: number, hidden: boolean): void;
90
+ /** The offset of an index's start: the sum of the effective sizes before it. O(log n). */
91
+ offsetOf(index: number): number;
92
+ /** The total extent of the axis. */
93
+ get extent(): number;
94
+ /** The index under an offset: the last index whose start is at or before it. O(log n). */
95
+ indexAt(offset: number): number;
96
+ }
97
+ //# sourceMappingURL=axis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"axis.d.ts","sourceRoot":"","sources":["../../src/core/axis.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;GAiBG;AAEH,yDAAyD;AACzD,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,yEAAyE;AACzE,eAAO,MAAM,UAAU,QAAU,CAAC;AAElC,0DAA0D;AAC1D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,qBAAa,IAAI;;IACf,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAcjB,OAAO,EAAE,WAAW;IAehC,oDAAoD;IACpD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAM3B;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAalC,qCAAqC;IACrC,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,oGAAoG;IACpG,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM;IAS7B,yEAAyE;IACzE,GAAG,IAAI,UAAU;IAMjB,oFAAoF;IACpF,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,WAAW;IAU3C;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG;QAAE,QAAQ,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,WAAW,CAAA;KAAE;IAkCrG;;;;OAIG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,WAAW;IA8B9D;;;OAGG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IA8B7C,6EAA6E;IAC7E,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAenG,kDAAkD;IAClD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAK7B,wDAAwD;IACxD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAItC,0CAA0C;IAC1C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAWzC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAMhC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAU/C,0FAA0F;IAC1F,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAU/B,oCAAoC;IACpC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,0FAA0F;IAC1F,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CA+FhC"}
@@ -0,0 +1,431 @@
1
+ /*
2
+ * Copyright (c) 2022-2026 Datalayer, Inc.
3
+ *
4
+ * MIT License
5
+ */
6
+ /**
7
+ * An axis of a sheet: its rows, or its columns.
8
+ *
9
+ * - **Ids.** Every index has a stable id that follows the row through inserts,
10
+ * deletes and moves. Ids are materialised only up to the last index ever
11
+ * named; beyond, an index has an *implicit* id derived from where the row
12
+ * was when the axis was created (`IMPLICIT_BASE + creation index`), so two
13
+ * replicas that materialise the same index independently agree on its id.
14
+ * Inserted indexes get fresh ids, from `FRESH_BASE` up.
15
+ * - **Sizes.** Default until the first resize; then a raw `Float64Array` of
16
+ * sizes and a Fenwick tree over the effective sizes (0 when hidden), so
17
+ * `offsetOf` and `indexAt` are O(log n) and a resize is O(log n). An insert
18
+ * or delete rebuilds the tree from the edit point only.
19
+ * - **Hidden** is a bitset beside the sizes: a hidden index keeps its size for
20
+ * unhide and contributes 0 to offsets.
21
+ *
22
+ * @module core/axis
23
+ */
24
+ /** Implicit ids are `IMPLICIT_BASE + creation index`. */
25
+ export const IMPLICIT_BASE = 0;
26
+ /** Fresh ids (inserted indexes) start here, above any creation index. */
27
+ export const FRESH_BASE = 1 << 30;
28
+ export class Axis {
29
+ count;
30
+ defaultSize;
31
+ /** Materialised ids, `[0, #length)`; the buffer grows by doubling. */
32
+ #ids = new Int32Array(0);
33
+ #length = 0;
34
+ /** The creation index of the first implicit index (`#length`). */
35
+ #implicitStart = 0;
36
+ #nextFresh = FRESH_BASE;
37
+ /** `null` until the first non-default size or hidden index. */
38
+ #sizes = null;
39
+ #tree = null;
40
+ #hidden = null;
41
+ constructor(options) {
42
+ if (!Number.isInteger(options.count) || options.count < 1 || options.count > FRESH_BASE - 1) {
43
+ throw new Error(`An axis holds 1 to ${FRESH_BASE - 1} indexes; ${options.count} was asked for.`);
44
+ }
45
+ if (!(options.defaultSize > 0)) {
46
+ throw new Error(`An axis's default size must be positive; ${options.defaultSize} was given.`);
47
+ }
48
+ this.count = options.count;
49
+ this.defaultSize = options.defaultSize;
50
+ }
51
+ // ---------------------------------------------------------------- ids
52
+ /** The id of an index, materialised or implicit. */
53
+ idAt(index) {
54
+ this.#check(index);
55
+ if (index < this.#length)
56
+ return this.#ids[index];
57
+ return IMPLICIT_BASE + this.#implicitStart + (index - this.#length);
58
+ }
59
+ /**
60
+ * Materialise ids up to `index` inclusive and return its id. Anything that
61
+ * names an index (content, a size, a merge corner, a reference) calls this
62
+ * first, so no implicit id is ever referenced.
63
+ */
64
+ materialize(index) {
65
+ this.#check(index);
66
+ if (index >= this.#length) {
67
+ const n = index + 1 - this.#length;
68
+ this.#reserve(index + 1);
69
+ for (let k = 0; k < n; k += 1)
70
+ this.#ids[this.#length + k] = IMPLICIT_BASE + this.#implicitStart + k;
71
+ this.#implicitStart += n;
72
+ this.#length = index + 1;
73
+ }
74
+ return this.#ids[index];
75
+ }
76
+ /** How many ids are materialised. */
77
+ get materialized() {
78
+ return this.#length;
79
+ }
80
+ /** The index of an id, or -1. O(n) over the materialised ids; for the binding, not the hot path. */
81
+ indexOfId(id) {
82
+ for (let i = 0; i < this.#length; i += 1)
83
+ if (this.#ids[i] === id)
84
+ return i;
85
+ if (id >= IMPLICIT_BASE + this.#implicitStart && id < FRESH_BASE) {
86
+ const index = this.#length + (id - IMPLICIT_BASE - this.#implicitStart);
87
+ return index < this.count ? index : -1;
88
+ }
89
+ return -1;
90
+ }
91
+ /** A copy of the materialised ids, for snapshots and the file format. */
92
+ ids() {
93
+ return this.#ids.slice(0, this.#length);
94
+ }
95
+ // ---------------------------------------------------------------- structure
96
+ /** The state of `n` indexes from `at`: ids, sizes and hidden flags, for patches. */
97
+ segment(at, n) {
98
+ this.#checkSpan(at, n, false);
99
+ this.materialize(at + n - 1);
100
+ return {
101
+ ids: this.#ids.slice(at, at + n),
102
+ sizes: this.#sizes === null ? null : this.#sizes.slice(at, at + n),
103
+ hidden: this.#hidden === null ? null : Uint8Array.from({ length: n }, (_, k) => (this.isHidden(at + k) ? 1 : 0)),
104
+ };
105
+ }
106
+ /**
107
+ * Insert `n` indexes before `at`. The count stays: the last `n` indexes fall
108
+ * off the end (the command checks they are empty), and come back as
109
+ * `dropped`. With `segment`, the inserted indexes take its ids, sizes and
110
+ * hidden flags (undo, redo, remote inserts); without, fresh ids and the
111
+ * default size.
112
+ */
113
+ insert(at, n, segment) {
114
+ this.#checkSpan(at, n, true);
115
+ if (segment !== undefined && segment.ids.length !== n) {
116
+ throw new Error(`insert: ${n} indexes but a segment of ${segment.ids.length}.`);
117
+ }
118
+ const dropped = this.segment(this.count - n, n);
119
+ let ids;
120
+ if (segment !== undefined)
121
+ ids = segment.ids.slice();
122
+ else {
123
+ ids = new Int32Array(n);
124
+ for (let k = 0; k < n; k += 1)
125
+ ids[k] = this.#nextFresh++;
126
+ }
127
+ if (at > this.#length)
128
+ this.materialize(at - 1);
129
+ this.#reserve(Math.min(this.#length + n, this.count));
130
+ this.#ids.copyWithin(at + n, at, Math.min(this.#length, this.count - n));
131
+ this.#ids.set(ids, at);
132
+ this.#length = Math.min(this.#length + n, this.count);
133
+ const needsSizes = this.#sizes !== null || segment?.sizes != null || segment?.hidden != null;
134
+ if (needsSizes) {
135
+ this.#allocate();
136
+ const sizes = this.#sizes;
137
+ sizes.copyWithin(at + n, at, this.count - n);
138
+ if (segment?.sizes)
139
+ sizes.set(segment.sizes, at);
140
+ else
141
+ sizes.fill(this.defaultSize, at, at + n);
142
+ if (this.#hidden !== null || segment?.hidden) {
143
+ if (this.#hidden === null)
144
+ this.#hidden = new Uint32Array(Math.ceil(this.count / 32));
145
+ shiftBits(this.#hidden, this.count, at, n);
146
+ if (segment?.hidden)
147
+ for (let k = 0; k < n; k += 1)
148
+ if (segment.hidden[k])
149
+ this.#setBit(at + k, true);
150
+ }
151
+ this.#rebuildFrom(at);
152
+ }
153
+ return { inserted: { ids, sizes: segment?.sizes ?? null, hidden: segment?.hidden ?? null }, dropped };
154
+ }
155
+ /**
156
+ * Delete `n` indexes from `at`; returns them. New indexes appear at the end:
157
+ * implicit ones, or `tail`'s when given (undoing an insert brings back the
158
+ * indexes it pushed off).
159
+ */
160
+ delete(at, n, tail) {
161
+ this.#checkSpan(at, n, false);
162
+ if (tail !== undefined && tail.ids.length !== n) {
163
+ throw new Error(`delete: ${n} indexes but a tail of ${tail.ids.length}.`);
164
+ }
165
+ const removed = this.segment(at, n);
166
+ if (tail !== undefined)
167
+ this.materialize(this.count - 1);
168
+ this.#ids.copyWithin(at, at + n, this.#length);
169
+ this.#length -= n;
170
+ if (tail !== undefined) {
171
+ this.#ids.set(tail.ids, this.count - n);
172
+ this.#length = this.count;
173
+ }
174
+ const needsSizes = this.#sizes !== null || tail?.sizes != null || tail?.hidden != null;
175
+ if (needsSizes) {
176
+ this.#allocate();
177
+ const sizes = this.#sizes;
178
+ sizes.copyWithin(at, at + n, this.count);
179
+ if (tail?.sizes)
180
+ sizes.set(tail.sizes, this.count - n);
181
+ else
182
+ sizes.fill(this.defaultSize, this.count - n, this.count);
183
+ if (this.#hidden !== null || tail?.hidden) {
184
+ if (this.#hidden === null)
185
+ this.#hidden = new Uint32Array(Math.ceil(this.count / 32));
186
+ shiftBits(this.#hidden, this.count, at, -n);
187
+ if (tail?.hidden)
188
+ for (let k = 0; k < n; k += 1)
189
+ if (tail.hidden[k])
190
+ this.#setBit(this.count - n + k, true);
191
+ }
192
+ this.#rebuildFrom(at);
193
+ }
194
+ return removed;
195
+ }
196
+ /**
197
+ * Move `n` indexes from `at` to before `to` (an index in the coordinates
198
+ * before the move, outside `[at, at + n]`). Ids, sizes and hidden flags travel.
199
+ */
200
+ move(at, n, to) {
201
+ this.#checkSpan(at, n, false);
202
+ if (!Number.isInteger(to) || to < 0 || to > this.count || (to >= at && to <= at + n)) {
203
+ throw new Error(`Cannot move ${n} index(es) at ${at} to ${to}.`);
204
+ }
205
+ const lo = Math.min(at, to);
206
+ const hi = Math.max(at + n, to);
207
+ this.materialize(hi - 1);
208
+ const rotate = (arr) => {
209
+ const part = arr.slice(at, at + n);
210
+ if (to < at) {
211
+ arr.copyWithin(to + n, to, at);
212
+ arr.set(part, to);
213
+ }
214
+ else {
215
+ arr.copyWithin(at, at + n, to);
216
+ arr.set(part, to - n);
217
+ }
218
+ };
219
+ rotate(this.#ids);
220
+ if (this.#sizes !== null) {
221
+ rotate(this.#sizes);
222
+ if (this.#hidden !== null) {
223
+ const flags = Float64Array.from({ length: this.count }, (_, i) => (i >= lo && i < hi && this.isHidden(i) ? 1 : 0));
224
+ rotate(flags);
225
+ for (let i = lo; i < hi; i += 1)
226
+ this.#setBit(i, flags[i] === 1);
227
+ }
228
+ this.#rebuildFrom(lo);
229
+ }
230
+ }
231
+ /** Set sizes and hidden flags of `n` indexes from `at` at once (patches). */
232
+ setSegmentSizes(at, sizes, hidden, n) {
233
+ this.#checkSpan(at, n, false);
234
+ if (sizes === null && hidden === null && this.#sizes === null)
235
+ return;
236
+ this.#allocate();
237
+ if (sizes)
238
+ this.#sizes.set(sizes, at);
239
+ else
240
+ this.#sizes.fill(this.defaultSize, at, at + n);
241
+ if (hidden || this.#hidden !== null) {
242
+ if (this.#hidden === null)
243
+ this.#hidden = new Uint32Array(Math.ceil(this.count / 32));
244
+ for (let k = 0; k < n; k += 1)
245
+ this.#setBit(at + k, hidden ? hidden[k] === 1 : false);
246
+ }
247
+ this.#rebuildFrom(at);
248
+ }
249
+ // ---------------------------------------------------------------- sizes
250
+ /** The size of an index as set, hidden or not. */
251
+ sizeOf(index) {
252
+ this.#check(index);
253
+ return this.#sizes === null ? this.defaultSize : this.#sizes[index];
254
+ }
255
+ /** The size an index takes on screen: 0 when hidden. */
256
+ effectiveSizeOf(index) {
257
+ return this.isHidden(index) ? 0 : this.sizeOf(index);
258
+ }
259
+ /** Set the size of an index. O(log n). */
260
+ resize(index, size) {
261
+ this.#check(index);
262
+ if (!(size > 0) || !Number.isFinite(size))
263
+ throw new Error(`A size must be positive; ${size} was given.`);
264
+ this.#allocate();
265
+ const before = this.effectiveSizeOf(index);
266
+ this.#sizes[index] = size;
267
+ const after = this.effectiveSizeOf(index);
268
+ if (after !== before)
269
+ this.#add(index, after - before);
270
+ }
271
+ isHidden(index) {
272
+ this.#check(index);
273
+ const h = this.#hidden;
274
+ return h !== null && (h[index >>> 5] & (1 << (index & 31))) !== 0;
275
+ }
276
+ setHidden(index, hidden) {
277
+ if (this.isHidden(index) === hidden)
278
+ return;
279
+ this.#allocate();
280
+ if (this.#hidden === null)
281
+ this.#hidden = new Uint32Array(Math.ceil(this.count / 32));
282
+ const size = this.#sizes[index];
283
+ if (hidden)
284
+ this.#hidden[index >>> 5] |= 1 << (index & 31);
285
+ else
286
+ this.#hidden[index >>> 5] &= ~(1 << (index & 31));
287
+ this.#add(index, hidden ? -size : size);
288
+ }
289
+ /** The offset of an index's start: the sum of the effective sizes before it. O(log n). */
290
+ offsetOf(index) {
291
+ if (!Number.isInteger(index) || index < 0 || index > this.count) {
292
+ throw new Error(`No offset at index ${index} of an axis of ${this.count}.`);
293
+ }
294
+ if (this.#tree === null)
295
+ return index * this.defaultSize;
296
+ let sum = 0;
297
+ for (let i = index; i > 0; i -= i & -i)
298
+ sum += this.#tree[i];
299
+ return sum;
300
+ }
301
+ /** The total extent of the axis. */
302
+ get extent() {
303
+ return this.offsetOf(this.count);
304
+ }
305
+ /** The index under an offset: the last index whose start is at or before it. O(log n). */
306
+ indexAt(offset) {
307
+ if (offset <= 0)
308
+ return this.#firstVisibleFrom(0);
309
+ if (this.#tree === null)
310
+ return Math.min(Math.floor(offset / this.defaultSize), this.count - 1);
311
+ const tree = this.#tree;
312
+ let pos = 0;
313
+ let rest = offset;
314
+ for (let step = highestPowerOfTwo(this.count); step > 0; step >>= 1) {
315
+ const next = pos + step;
316
+ if (next <= this.count && tree[next] <= rest) {
317
+ pos = next;
318
+ rest -= tree[next];
319
+ }
320
+ }
321
+ // `pos` indexes end at or before the offset; the one under it is `pos`,
322
+ // or the last index when the offset is past the end.
323
+ return Math.min(pos, this.count - 1);
324
+ }
325
+ // ---------------------------------------------------------------- internals
326
+ #firstVisibleFrom(index) {
327
+ let i = index;
328
+ while (i < this.count - 1 && this.effectiveSizeOf(i) === 0)
329
+ i += 1;
330
+ return i;
331
+ }
332
+ #allocate() {
333
+ if (this.#sizes !== null)
334
+ return;
335
+ this.#sizes = new Float64Array(this.count).fill(this.defaultSize);
336
+ this.#tree = new Float64Array(this.count + 1);
337
+ this.#rebuildFrom(0);
338
+ }
339
+ #setBit(index, on) {
340
+ const h = this.#hidden;
341
+ if (on)
342
+ h[index >>> 5] |= 1 << (index & 31);
343
+ else
344
+ h[index >>> 5] &= ~(1 << (index & 31));
345
+ }
346
+ #add(index, delta) {
347
+ const tree = this.#tree;
348
+ for (let i = index + 1; i <= this.count; i += i & -i)
349
+ tree[i] += delta;
350
+ }
351
+ /**
352
+ * Recompute the Fenwick nodes covering indexes `>= from`, in one pass:
353
+ * node i holds prefix(i) - prefix(i - lowbit(i)); prefixes below `from`
354
+ * come from the still-valid lower nodes, those above from a running sum.
355
+ */
356
+ #rebuildFrom(from) {
357
+ const tree = this.#tree;
358
+ const n = this.count;
359
+ const start = from + 1;
360
+ const below = this.#prefixFromTree(from);
361
+ const running = new Float64Array(n - from + 1);
362
+ running[0] = below;
363
+ let sum = below;
364
+ for (let i = start; i <= n; i += 1) {
365
+ sum += this.effectiveSizeOf(i - 1);
366
+ running[i - from] = sum;
367
+ const lower = i - (i & -i);
368
+ const lowerPrefix = lower >= from ? running[lower - from] : this.#prefixFromTree(lower);
369
+ tree[i] = sum - lowerPrefix;
370
+ }
371
+ }
372
+ #prefixFromTree(index) {
373
+ const tree = this.#tree;
374
+ let sum = 0;
375
+ for (let i = index; i > 0; i -= i & -i)
376
+ sum += tree[i];
377
+ return sum;
378
+ }
379
+ #reserve(length) {
380
+ if (length <= this.#ids.length)
381
+ return;
382
+ let capacity = Math.max(64, this.#ids.length);
383
+ while (capacity < length)
384
+ capacity *= 2;
385
+ const ids = new Int32Array(Math.min(capacity, this.count + 1));
386
+ ids.set(this.#ids.subarray(0, this.#length));
387
+ this.#ids = ids;
388
+ }
389
+ #check(index) {
390
+ if (!Number.isInteger(index) || index < 0 || index >= this.count) {
391
+ throw new Error(`Index ${index} is outside an axis of ${this.count}.`);
392
+ }
393
+ }
394
+ #checkSpan(at, n, inserting) {
395
+ if (!Number.isInteger(n) || n < 1)
396
+ throw new Error(`A span needs at least one index; ${n} was given.`);
397
+ if (!Number.isInteger(at) || at < 0 || at + n > this.count || (inserting && at >= this.count)) {
398
+ throw new Error(`${n} index(es) at ${at} do not fit an axis of ${this.count}.`);
399
+ }
400
+ }
401
+ }
402
+ function highestPowerOfTwo(n) {
403
+ let p = 1;
404
+ while (p * 2 <= n)
405
+ p *= 2;
406
+ return p;
407
+ }
408
+ /** Shift the bits at and after `at` by `n` (negative: towards `at`, dropping `-n` bits). */
409
+ function shiftBits(bits, count, at, n) {
410
+ const get = (i) => (bits[i >>> 5] & (1 << (i & 31))) !== 0;
411
+ const set = (i, v) => {
412
+ if (v)
413
+ bits[i >>> 5] |= 1 << (i & 31);
414
+ else
415
+ bits[i >>> 5] &= ~(1 << (i & 31));
416
+ };
417
+ if (n > 0) {
418
+ for (let i = count - 1; i >= at + n; i -= 1)
419
+ set(i, get(i - n));
420
+ for (let i = at; i < at + n; i += 1)
421
+ set(i, false);
422
+ }
423
+ else {
424
+ const m = -n;
425
+ for (let i = at; i < count - m; i += 1)
426
+ set(i, get(i + m));
427
+ for (let i = count - m; i < count; i += 1)
428
+ set(i, false);
429
+ }
430
+ }
431
+ //# sourceMappingURL=axis.js.map