@orkestrel/table 0.0.8 → 0.0.10

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.
@@ -0,0 +1,1058 @@
1
+ import { arrayOf, attempt, cloneJSONRecord, isArray, isBoolean, isBoundedJSONRecord, isContractError, isFiniteNumber, isInstance, isRecord, isString, parseJSON, parseNumber, readArrayEntries, recordOf } from "@orkestrel/contract";
2
+ import { Emitter } from "@orkestrel/emitter";
3
+ //#region src/browser/errors.ts
4
+ /** Represents a native table binding failure with a machine-readable code. */
5
+ var TableBindingError = class extends Error {
6
+ code;
7
+ /** Creates a binding failure.
8
+ * @param code - The failed boundary.
9
+ * @param message - The explanation.
10
+ */
11
+ constructor(code, message) {
12
+ super(message);
13
+ this.name = "TableBindingError";
14
+ this.code = code;
15
+ }
16
+ };
17
+ /** Checks whether a caught value is a native table binding error.
18
+ * @param value - The caught value.
19
+ * @returns Whether the value is a binding error.
20
+ */
21
+ function isTableBindingError(value) {
22
+ return isInstance(value, TableBindingError);
23
+ }
24
+ //#endregion
25
+ //#region src/core/errors.ts
26
+ /**
27
+ * Represents an error raised by the table domain — a machine-readable `code` and optional
28
+ * structured `context`.
29
+ */
30
+ var TableError = class extends Error {
31
+ /** Holds the machine-readable reason for this failure. */
32
+ code;
33
+ /** Holds structured values that locate or explain this failure. */
34
+ context;
35
+ /**
36
+ * Creates a table error.
37
+ *
38
+ * @param code - The machine-readable reason.
39
+ * @param message - The human-readable failure text.
40
+ * @param context - Optional structured failure details.
41
+ */
42
+ constructor(code, message, context) {
43
+ super(message);
44
+ this.name = "TableError";
45
+ this.code = code;
46
+ if (context !== void 0) this.context = context;
47
+ }
48
+ };
49
+ //#endregion
50
+ //#region src/core/cloners.ts
51
+ /**
52
+ * Clones one row into an owned frozen snapshot.
53
+ *
54
+ * @param row - The row to own.
55
+ * @returns A frozen copy of the row's cells.
56
+ */
57
+ function cloneRow(row) {
58
+ return Object.freeze({ ...row });
59
+ }
60
+ //#endregion
61
+ //#region src/core/constants.ts
62
+ /** Lists every column cell, in the order declared by the public contract. */
63
+ var COLUMN_CELLS = Object.freeze([
64
+ "text",
65
+ "number",
66
+ "flag",
67
+ "choice"
68
+ ]);
69
+ /** Names the maximum number of choices one `choice` column may offer: 1024. */
70
+ var CHOICE_LIMIT = 1024;
71
+ /** Names the maximum length of any single retained string: 65536 UTF-16 code units. */
72
+ var STRING_LIMIT = 65536;
73
+ /**
74
+ * Names the maximum total length of every string one schema retains: 1048576 UTF-16 code units.
75
+ */
76
+ var TEXT_LIMIT = 1048576;
77
+ /** Names the maximum total number of records, arrays, and leaves one schema retains: 16384. */
78
+ var NODE_LIMIT = 16384;
79
+ //#endregion
80
+ //#region src/core/helpers.ts
81
+ /**
82
+ * Finds one column by key; `undefined` when the schema declares no such column.
83
+ *
84
+ * @param schema - The schema whose columns to search.
85
+ * @param key - The column key to find.
86
+ * @returns The declared column, or `undefined` when no column has that key.
87
+ */
88
+ function extractColumn(schema, key) {
89
+ return schema.columns.find((column) => column.key === key);
90
+ }
91
+ /**
92
+ * Reads one row's declared identity; `undefined` when its key cell is missing, empty, or not a
93
+ * string.
94
+ *
95
+ * @param schema - The schema that names the identity column.
96
+ * @param row - The row whose identity to read.
97
+ * @returns The non-empty string identity, or `undefined` when it is unusable.
98
+ */
99
+ function extractKey(schema, row) {
100
+ if (!Object.hasOwn(row, schema.key)) return void 0;
101
+ const key = row[schema.key];
102
+ return isString(key) && key.length > 0 ? key : void 0;
103
+ }
104
+ /**
105
+ * Checks whether one column can hold a value — the shape gate every write and every seed passes
106
+ * through.
107
+ *
108
+ * @param column - The column that owns the cell.
109
+ * @param value - The unknown value to inspect.
110
+ * @returns True if the column can hold the value; false otherwise.
111
+ */
112
+ function matchesCell(column, value) {
113
+ if (isString(value) && value.length > 65536) return false;
114
+ switch (column.cell) {
115
+ case "text": return isString(value);
116
+ case "number": return isFiniteNumber(value);
117
+ case "flag": return isBoolean(value);
118
+ case "choice": return isString(value) && column.choices.some((choice) => choice.value === value);
119
+ }
120
+ }
121
+ /**
122
+ * Audits a structurally valid schema for domain faults and budget breaches, returning human
123
+ * diagnostics.
124
+ *
125
+ * @param schema - The table schema to audit.
126
+ * @returns Frozen human-readable diagnostics, or an empty list when the schema is sound.
127
+ */
128
+ function auditTable(schema) {
129
+ const faults = [];
130
+ const columns = /* @__PURE__ */ new Set();
131
+ let choiceExceeded;
132
+ let nameExceeded = schema.name !== void 0 && schema.name.length > 128;
133
+ if (schema.columns.length > 256) faults.push(`schema declares more than 256 columns`);
134
+ const columnCount = Math.min(schema.columns.length, 257);
135
+ for (let index = 0; index < columnCount; index += 1) {
136
+ const column = schema.columns[index];
137
+ if (column === void 0) continue;
138
+ if (column.key.length > 128) nameExceeded = true;
139
+ if (choiceExceeded === void 0 && column.cell === "choice" && column.choices.length > 1024) choiceExceeded = column.key;
140
+ }
141
+ if (choiceExceeded !== void 0) faults.push(`column "${choiceExceeded}" offers more than ${CHOICE_LIMIT} choices`);
142
+ if (nameExceeded) faults.push(`schema contains a name longer than 128`);
143
+ const pending = [schema];
144
+ const metadata = [false];
145
+ let position = 0;
146
+ let stringExceeded = false;
147
+ let textExceeded = false;
148
+ let nodeExceeded = false;
149
+ let text = 0;
150
+ while (position < pending.length) {
151
+ const node = pending[position];
152
+ const inMeta = metadata[position] === true;
153
+ position += 1;
154
+ if (isString(node)) {
155
+ if (node.length > 65536) stringExceeded = true;
156
+ text = Math.min(TEXT_LIMIT + 1, text + node.length);
157
+ if (text > 1048576) textExceeded = true;
158
+ continue;
159
+ }
160
+ if (isArray(node)) {
161
+ const read = readArrayEntries(node);
162
+ if (!read.success || !read.value.dense) continue;
163
+ for (const entry of read.value.entries) {
164
+ if (pending.length >= 16384) {
165
+ nodeExceeded = true;
166
+ continue;
167
+ }
168
+ pending.push(entry);
169
+ metadata.push(inMeta);
170
+ }
171
+ continue;
172
+ }
173
+ if (!isRecord(node)) continue;
174
+ const keys = attempt(() => Object.keys(node));
175
+ if (!keys.success) continue;
176
+ for (const key of keys.value) {
177
+ if (inMeta) {
178
+ if (key.length > 65536) stringExceeded = true;
179
+ text = Math.min(TEXT_LIMIT + 1, text + key.length);
180
+ if (text > 1048576) textExceeded = true;
181
+ }
182
+ const value = attempt(() => node[key]);
183
+ if (!value.success || value.value === void 0) continue;
184
+ if (pending.length >= 16384) {
185
+ nodeExceeded = true;
186
+ continue;
187
+ }
188
+ pending.push(value.value);
189
+ metadata.push(inMeta || key === "meta");
190
+ }
191
+ }
192
+ if (stringExceeded) faults.push(`schema contains a string longer than ${STRING_LIMIT}`);
193
+ if (textExceeded) faults.push(`schema retains more than ${TEXT_LIMIT} string code units`);
194
+ if (nodeExceeded) faults.push(`schema retains more than ${NODE_LIMIT} nodes`);
195
+ for (let index = 0; index < columnCount; index += 1) {
196
+ const column = schema.columns[index];
197
+ if (column === void 0) continue;
198
+ if (!nodeExceeded && column.meta !== void 0) {
199
+ if (!attempt(() => cloneJSONRecord(column.meta)).success) faults.push(`column "${column.key}" has metadata that cannot be owned`);
200
+ }
201
+ if (column.key.length === 0) faults.push("column \"\" has an empty key");
202
+ if (columns.has(column.key)) faults.push(`column "${column.key}" is declared more than once`);
203
+ columns.add(column.key);
204
+ if (column.cell === "choice") {
205
+ const choices = /* @__PURE__ */ new Set();
206
+ const choiceCount = Math.min(column.choices.length, CHOICE_LIMIT + 1);
207
+ for (let choiceIndex = 0; choiceIndex < choiceCount; choiceIndex += 1) {
208
+ const choice = column.choices[choiceIndex];
209
+ if (choice === void 0) continue;
210
+ if (choices.has(choice.value)) faults.push(`column "${column.key}" offers choice "${choice.value}" more than once`);
211
+ choices.add(choice.value);
212
+ }
213
+ if (column.choices.length === 0) faults.push(`column "${column.key}" offers no choices`);
214
+ }
215
+ }
216
+ const key = extractColumn(schema, schema.key);
217
+ if (key === void 0) faults.push(`schema key "${schema.key}" names no declared column`);
218
+ else if (key.cell === "number" || key.cell === "flag") faults.push(`schema key "${schema.key}" names a ${key.cell} column, which holds no identity`);
219
+ return Object.freeze(faults);
220
+ }
221
+ /**
222
+ * Projects a schema into JSON in declaration order, dropping every absent member; raises `SCHEMA`
223
+ * for a `meta` it cannot own.
224
+ *
225
+ * @param schema - The schema to project.
226
+ * @returns A deeply owned JSON record with absent members omitted.
227
+ * @throws A {@link TableError} coded `SCHEMA` when metadata cannot be owned.
228
+ */
229
+ function serializeTable(schema) {
230
+ const output = {};
231
+ if (schema.name !== void 0) output.name = schema.name;
232
+ if (schema.label !== void 0) output.label = schema.label;
233
+ if (schema.help !== void 0) output.help = schema.help;
234
+ output.key = schema.key;
235
+ output.columns = schema.columns.map((column) => {
236
+ const entry = {
237
+ cell: column.cell,
238
+ key: column.key
239
+ };
240
+ if (column.label !== void 0) entry.label = column.label;
241
+ if (column.help !== void 0) entry.help = column.help;
242
+ if (column.hidden !== void 0) entry.hidden = column.hidden;
243
+ if (column.meta !== void 0) entry.meta = column.meta;
244
+ if (column.cell === "choice") entry.choices = column.choices.map((choice) => {
245
+ const option = {
246
+ value: choice.value,
247
+ label: choice.label
248
+ };
249
+ if (choice.help !== void 0) option.help = choice.help;
250
+ return option;
251
+ });
252
+ return entry;
253
+ });
254
+ try {
255
+ return cloneJSONRecord(output);
256
+ } catch (error) {
257
+ if (!isContractError(error)) throw error;
258
+ throw new TableError("SCHEMA", "schema contains metadata that cannot be owned");
259
+ }
260
+ }
261
+ /** Compares complete row values independently of their snapshot identities.
262
+ * @param left - The previous row, if present.
263
+ * @param right - The next row.
264
+ * @returns Whether both rows carry exactly the same cells.
265
+ */
266
+ function matchesTableRow(left, right) {
267
+ return left !== void 0 && Object.keys(left).length === Object.keys(right).length && Object.keys(right).every((key) => Object.hasOwn(left, key) && left[key] === right[key]);
268
+ }
269
+ //#endregion
270
+ //#region src/core/validators.ts
271
+ /**
272
+ * Determines whether an unknown value is a declared column cell.
273
+ *
274
+ * @param input - The value to inspect.
275
+ * @returns True if the value is a declared column cell; false otherwise.
276
+ */
277
+ function isColumnCell(input) {
278
+ return COLUMN_CELLS.some((cell) => cell === input);
279
+ }
280
+ /**
281
+ * Determines whether an unknown value is one exact {@link ColumnChoice} record; an unknown member
282
+ * refuses it.
283
+ *
284
+ * @param input - The value to inspect.
285
+ * @returns True if the value is a column choice; false otherwise.
286
+ */
287
+ function isColumnChoice(input) {
288
+ const outcome = attempt(() => {
289
+ if (!isRecord(input) || !Reflect.ownKeys(input).every((key) => isString(key))) return false;
290
+ return recordOf({
291
+ value: isString,
292
+ label: isString,
293
+ help: isString
294
+ }, ["help"])(input);
295
+ });
296
+ return outcome.success && outcome.value;
297
+ }
298
+ /**
299
+ * Determines whether an unknown value is one exact discriminated {@link TableColumn}, checked
300
+ * against its cell's own options.
301
+ *
302
+ * @param input - The value to inspect.
303
+ * @returns True if the value is a structurally valid table column; false otherwise.
304
+ */
305
+ function isTableColumn(input) {
306
+ const outcome = attempt(() => {
307
+ if (!isRecord(input) || !Object.hasOwn(input, "cell") || !Object.hasOwn(input, "key")) return false;
308
+ const cell = input.cell;
309
+ if (!isColumnCell(cell)) return false;
310
+ if (!Reflect.ownKeys(input).every((key) => {
311
+ if (!isString(key)) return false;
312
+ if ([
313
+ "cell",
314
+ "key",
315
+ "label",
316
+ "help",
317
+ "hidden",
318
+ "meta"
319
+ ].includes(key)) return true;
320
+ return cell === "choice" && key === "choices";
321
+ })) return false;
322
+ const key = input.key;
323
+ const hasLabel = Object.hasOwn(input, "label");
324
+ const label = hasLabel ? input.label : void 0;
325
+ const hasHelp = Object.hasOwn(input, "help");
326
+ const help = hasHelp ? input.help : void 0;
327
+ const hasHidden = Object.hasOwn(input, "hidden");
328
+ const hidden = hasHidden ? input.hidden : void 0;
329
+ const hasMeta = Object.hasOwn(input, "meta");
330
+ const meta = hasMeta ? input.meta : void 0;
331
+ if (hasMeta) {
332
+ if (!isBoundedJSONRecord(meta)) return false;
333
+ if (!attempt(() => cloneJSONRecord(meta)).success) return false;
334
+ }
335
+ if (!isString(key) || hasLabel && !isString(label) || hasHelp && !isString(help) || hasHidden && !isBoolean(hidden)) return false;
336
+ if (cell !== "choice") return !Object.hasOwn(input, "choices");
337
+ return Object.hasOwn(input, "choices") && arrayOf(isColumnChoice)(input.choices);
338
+ });
339
+ return outcome.success && outcome.value;
340
+ }
341
+ /**
342
+ * Determines whether an unknown value has the exact shape of a {@link TableSchema} — the shape
343
+ * alone, with no domain check.
344
+ *
345
+ * @param input - The value to inspect.
346
+ * @returns True if the value has the exact structure of a table schema; false otherwise.
347
+ */
348
+ function isStructuralTableSchema(input) {
349
+ const outcome = attempt(() => {
350
+ if (!isRecord(input) || !Reflect.ownKeys(input).every((key) => isString(key))) return false;
351
+ return recordOf({
352
+ name: isString,
353
+ label: isString,
354
+ help: isString,
355
+ key: isString,
356
+ columns: arrayOf(isTableColumn)
357
+ }, [
358
+ "name",
359
+ "label",
360
+ "help"
361
+ ])(input);
362
+ });
363
+ return outcome.success && outcome.value;
364
+ }
365
+ /**
366
+ * Determines whether an unknown value is a {@link TableSchema} a table can be opened against —
367
+ * the exact shape, and an audit that finds nothing.
368
+ *
369
+ * @param input - The value to inspect.
370
+ * @returns True if the value has valid structure, domain relationships, and
371
+ * budgets; false otherwise.
372
+ */
373
+ function isTableSchema(input) {
374
+ const outcome = attempt(() => isStructuralTableSchema(input) && auditTable(input).length === 0);
375
+ return outcome.success && outcome.value;
376
+ }
377
+ //#endregion
378
+ //#region src/core/parsers.ts
379
+ /**
380
+ * Parses unknown wire data into an owned, structurally valid, semantically sound table schema.
381
+ *
382
+ * @param input - The unknown schema value to parse.
383
+ * @returns An owned table schema, or `undefined` on refusal.
384
+ */
385
+ function parseTable(input) {
386
+ const outcome = attempt(() => {
387
+ if (!isTableSchema(input)) return void 0;
388
+ const projected = serializeTable(input);
389
+ return isTableSchema(projected) ? projected : void 0;
390
+ });
391
+ return outcome.success ? outcome.value : void 0;
392
+ }
393
+ /**
394
+ * Parses unknown wire data into owned rows against one table schema, coercing a numeric string
395
+ * and `'true'` / `'false'`.
396
+ *
397
+ * @param schema - The schema that declares the accepted keys and cell shapes.
398
+ * @param input - The unknown row-list value to parse.
399
+ * @returns Frozen owned rows, or `undefined` when any row is refused.
400
+ */
401
+ function parseRows(schema, input) {
402
+ const outcome = attempt(() => {
403
+ if (!isTableSchema(schema) || !isArray(input)) return;
404
+ const read = readArrayEntries(input);
405
+ if (!read.success || !read.value.dense) return void 0;
406
+ const keys = /* @__PURE__ */ new Set();
407
+ const rows = [];
408
+ for (const candidate of read.value.entries) {
409
+ if (!isRecord(candidate)) return void 0;
410
+ const row = {};
411
+ for (const key of Reflect.ownKeys(candidate)) {
412
+ if (!isString(key) || !Object.hasOwn(candidate, key)) return void 0;
413
+ const column = extractColumn(schema, key);
414
+ if (column === void 0) return void 0;
415
+ const inputCell = candidate[key];
416
+ let cell = inputCell;
417
+ if (column.cell === "number" && isString(inputCell)) {
418
+ if (inputCell.length > 65536) return void 0;
419
+ cell = parseNumber(inputCell);
420
+ } else if (column.cell === "flag" && inputCell === "true") cell = true;
421
+ else if (column.cell === "flag" && inputCell === "false") cell = false;
422
+ if (!matchesCell(column, cell)) return void 0;
423
+ Object.defineProperty(row, key, {
424
+ value: cell,
425
+ enumerable: true,
426
+ configurable: true,
427
+ writable: true
428
+ });
429
+ }
430
+ const owned = cloneRow(row);
431
+ const key = extractKey(schema, owned);
432
+ if (key === void 0 || keys.has(key)) return void 0;
433
+ keys.add(key);
434
+ rows.push(owned);
435
+ }
436
+ return Object.freeze(rows);
437
+ });
438
+ return outcome.success ? outcome.value : void 0;
439
+ }
440
+ //#endregion
441
+ //#region src/browser/helpers.ts
442
+ /** Reads declared header metadata and explicit row keys into a validated core projection.
443
+ * @param element - A native table with data-table-key naming its identity column.
444
+ * @returns The schema and rows, validated together before any mutation.
445
+ * @throws A binding error when metadata, identities, or cells are malformed.
446
+ * @example
447
+ * ```ts
448
+ * import { createTable } from '@orkestrel/table'
449
+ * import { readTable } from '@orkestrel/table/browser'
450
+ * const element = document.createElement('table')
451
+ * element.setAttribute('data-table-key', 'id')
452
+ * element.innerHTML = '<thead><tr><th data-table-column="id" data-table-cell="text">Reference</th></tr></thead><tbody></tbody>'
453
+ * const projection = readTable(element)
454
+ * const table = createTable(projection.schema, { rows: projection.rows })
455
+ * ```
456
+ */
457
+ function readTable(element) {
458
+ const schema = readTableSchema(element);
459
+ return {
460
+ schema,
461
+ rows: readTableRows(schema, Array.from(element.tBodies[0]?.rows ?? []))
462
+ };
463
+ }
464
+ /** Reads and validates explicit native header metadata.
465
+ * @param element - The table declaring its identity column and cell kinds.
466
+ * @returns The core schema described by the native headers.
467
+ */
468
+ function readTableSchema(element) {
469
+ const columns = Array.from(element.tHead?.querySelectorAll("th[data-table-column]") ?? []).filter((header) => header.closest("table") === element).map((header) => ({
470
+ key: header.getAttribute("data-table-column"),
471
+ cell: header.getAttribute("data-table-cell"),
472
+ label: header.textContent?.trim() ?? "",
473
+ ...header.hasAttribute("data-table-choices") ? { choices: parseJSON(header.getAttribute("data-table-choices") ?? "") } : {}
474
+ }));
475
+ const schema = parseTable({
476
+ key: element.getAttribute("data-table-key"),
477
+ columns
478
+ });
479
+ if (schema === void 0 || element.tBodies.length !== 1) throw new TableBindingError("STRUCTURE", "Declare one body, an identity column, and valid header metadata");
480
+ return schema;
481
+ }
482
+ /** Reads explicit keys and declared direct cells against a core schema.
483
+ * @param schema - The declared columns and identity column.
484
+ * @param elements - Data rows belonging to one table, including retained detached rows.
485
+ * @returns Validated rows in the supplied order.
486
+ * @throws A binding error for duplicate keys, undeclared cells, or malformed values.
487
+ */
488
+ function readTableRows(schema, elements) {
489
+ const rows = parseRows(schema, elements.map((element) => {
490
+ const cells = { [schema.key]: element.getAttribute("data-table-key") };
491
+ const seen = /* @__PURE__ */ new Set();
492
+ for (const cell of element.cells) {
493
+ const column = cell.getAttribute("data-table-column");
494
+ if (column === null) continue;
495
+ if (seen.has(column)) throw new TableBindingError("STRUCTURE", "A row repeats a declared cell");
496
+ seen.add(column);
497
+ const value = cell.querySelector("data[value], time[datetime]");
498
+ const text = value?.closest("table") === element.closest("table") ? value?.getAttribute("value") ?? value?.getAttribute("datetime") ?? "" : cell.textContent ?? "";
499
+ if (column === schema.key && text !== cells[schema.key]) throw new TableBindingError("STRUCTURE", "The identity cell disagrees with its row key");
500
+ Object.defineProperty(cells, column, {
501
+ value: text,
502
+ enumerable: true,
503
+ configurable: true
504
+ });
505
+ }
506
+ return cells;
507
+ }));
508
+ if (rows === void 0) throw new TableBindingError("STRUCTURE", "Native row keys or cells are invalid");
509
+ return rows;
510
+ }
511
+ //#endregion
512
+ //#region src/browser/TableBinding.ts
513
+ /** Retains keyed native rows while projecting every core table manager. */
514
+ var TableBinding = class {
515
+ #element;
516
+ #document;
517
+ #options;
518
+ #body;
519
+ #scope;
520
+ #emitter;
521
+ #lifetime = new AbortController();
522
+ #observer;
523
+ #rows = /* @__PURE__ */ new Map();
524
+ #values = /* @__PURE__ */ new Map();
525
+ #details = /* @__PURE__ */ new Map();
526
+ #detailValues = /* @__PURE__ */ new Map();
527
+ #visible = /* @__PURE__ */ new Set();
528
+ #attributes = /* @__PURE__ */ new Map();
529
+ #checks = /* @__PURE__ */ new Map();
530
+ #controlValues = /* @__PURE__ */ new Map();
531
+ #schedule = this.#enqueue.bind(this);
532
+ #subscriptions = [];
533
+ #pending;
534
+ #announcement;
535
+ #range;
536
+ #rendering = false;
537
+ #requesting = false;
538
+ #records = [];
539
+ /** Creates a native binding over a borrowed document.
540
+ * @param element - The table whose body the binding owns.
541
+ * @param document - The core document retained by the caller.
542
+ * @param options - Row ownership, detail renderer, controls, and listeners.
543
+ */
544
+ constructor(element, document, options = {}) {
545
+ if (element.tBodies.length !== 1 || document.destroyed) throw new TableBindingError("STRUCTURE", "Bind one table body to a live document");
546
+ const body = element.tBodies[0];
547
+ if (body === void 0) throw new TableBindingError("STRUCTURE", "The table has no body");
548
+ this.#element = element;
549
+ this.#document = document;
550
+ this.#options = { ...options };
551
+ this.#body = body;
552
+ this.#scope = options.scope ?? element;
553
+ if (!this.#scope.contains(element)) throw new TableBindingError("STRUCTURE", "The scope must contain the table");
554
+ this.#emitter = new Emitter({
555
+ ...options.on === void 0 ? {} : { on: options.on },
556
+ ...options.error === void 0 ? {} : { error: options.error }
557
+ });
558
+ this.#observer = new MutationObserver((records) => {
559
+ this.#records.push(...records);
560
+ this.#enqueue();
561
+ });
562
+ if (options.row === void 0) {
563
+ this.#validateSchema();
564
+ const elements = Array.from(body.rows);
565
+ const rows = readTableRows(document.schema, elements);
566
+ if (rows.length !== document.rows.rows().length || rows.some((row) => {
567
+ const key = extractKey(document.schema, row);
568
+ return key === void 0 || !matchesTableRow(document.rows.row(key), row);
569
+ })) throw new TableBindingError("STRUCTURE", "Authored rows must match the supplied document");
570
+ for (const [index, row] of rows.entries()) {
571
+ const key = extractKey(document.schema, row);
572
+ const node = elements[index];
573
+ if (key !== void 0 && node !== void 0) this.#rows.set(key, node);
574
+ }
575
+ } else if (body.rows.length > 0) throw new TableBindingError("STRUCTURE", "Model rendering requires an empty body");
576
+ for (const event of [
577
+ "write",
578
+ "remove",
579
+ "sort",
580
+ "filter",
581
+ "select",
582
+ "expand",
583
+ "paginate",
584
+ "clear"
585
+ ]) {
586
+ document.emitter.on(event, this.#schedule);
587
+ this.#subscriptions.push(() => document.emitter.off(event, this.#schedule));
588
+ }
589
+ this.#scope.addEventListener("click", (event) => this.#click(event), { signal: this.#lifetime.signal });
590
+ this.#scope.addEventListener("change", (event) => this.#change(event), { signal: this.#lifetime.signal });
591
+ this.#observe();
592
+ this.refresh();
593
+ }
594
+ get element() {
595
+ return this.#element;
596
+ }
597
+ get document() {
598
+ return this.#document;
599
+ }
600
+ get destroyed() {
601
+ return this.#lifetime.signal.aborted;
602
+ }
603
+ get emitter() {
604
+ return this.#emitter;
605
+ }
606
+ /** Reconciles native input and retries rendering against the current document. */
607
+ refresh() {
608
+ if (this.destroyed) return;
609
+ if (this.#rendering) {
610
+ this.#enqueue();
611
+ return;
612
+ }
613
+ if (this.#pending !== void 0) clearTimeout(this.#pending);
614
+ this.#pending = void 0;
615
+ this.#rendering = true;
616
+ const outcome = attempt(() => {
617
+ this.#records.push(...this.#observer.takeRecords());
618
+ if (this.#options.row === void 0 && this.#records.length > 0) this.#reconcile();
619
+ if (!this.destroyed) this.#render();
620
+ });
621
+ if (!outcome.success && !this.destroyed) this.#emitter.emit("error", outcome.error);
622
+ else if (!this.destroyed) this.#emitter.emit("render");
623
+ this.#rendering = false;
624
+ }
625
+ /** Releases native resources and restores authored state while retaining the core document. */
626
+ destroy() {
627
+ if (this.destroyed) return;
628
+ const active = this.#element.ownerDocument.activeElement;
629
+ const focused = active instanceof HTMLElement && [...this.#rows.values()].some((row) => row.contains(active)) ? active : void 0;
630
+ this.#lifetime.abort();
631
+ if (this.#pending !== void 0) clearTimeout(this.#pending);
632
+ this.#pending = void 0;
633
+ this.#observer.disconnect();
634
+ if (this.#announcement !== void 0) clearTimeout(this.#announcement);
635
+ this.#announcement = void 0;
636
+ for (const unsubscribe of this.#subscriptions) unsubscribe();
637
+ this.#subscriptions.length = 0;
638
+ for (const detail of this.#details.values()) detail.remove();
639
+ if (this.#options.row === void 0) for (const row of this.#document.rows.rows()) {
640
+ const key = extractKey(this.#document.schema, row);
641
+ const node = key === void 0 ? void 0 : this.#rows.get(key);
642
+ if (node !== void 0) this.#place(node, null);
643
+ }
644
+ for (const element of this.#attributes.keys()) this.#release(element);
645
+ for (const input of this.#checks.keys()) this.#release(input);
646
+ for (const [control, state] of this.#controlValues) if (control.value === state.value) control.value = state.original;
647
+ this.#restoreFocus(focused);
648
+ this.#rows.clear();
649
+ this.#values.clear();
650
+ this.#details.clear();
651
+ this.#detailValues.clear();
652
+ this.#attributes.clear();
653
+ this.#checks.clear();
654
+ this.#controlValues.clear();
655
+ this.#visible.clear();
656
+ this.#records = [];
657
+ this.#emitter.destroy();
658
+ }
659
+ #enqueue() {
660
+ if (this.destroyed || this.#pending !== void 0) return;
661
+ this.#pending = setTimeout(() => {
662
+ this.#pending = void 0;
663
+ this.refresh();
664
+ }, 0);
665
+ }
666
+ #observe() {
667
+ if (this.destroyed || this.#options.row !== void 0) return;
668
+ this.#records.push(...this.#observer.takeRecords());
669
+ this.#observer.disconnect();
670
+ this.#observer.observe(this.#element, {
671
+ childList: true,
672
+ characterData: true,
673
+ subtree: true,
674
+ attributes: true,
675
+ attributeFilter: [
676
+ "data-table-key",
677
+ "data-table-column",
678
+ "data-table-cell",
679
+ "data-table-choices",
680
+ "value",
681
+ "datetime"
682
+ ]
683
+ });
684
+ for (const node of this.#rows.values()) if (node.parentElement !== this.#body) this.#observer.observe(node, {
685
+ childList: true,
686
+ characterData: true,
687
+ subtree: true,
688
+ attributes: true,
689
+ attributeFilter: [
690
+ "data-table-key",
691
+ "data-table-column",
692
+ "value",
693
+ "datetime"
694
+ ]
695
+ });
696
+ }
697
+ #validateSchema() {
698
+ const schema = readTableSchema(this.#element);
699
+ if (schema.key !== this.#document.schema.key || schema.columns.length !== this.#document.schema.columns.length || schema.columns.some((column, index) => {
700
+ const declared = this.#document.schema.columns[index];
701
+ return declared?.key !== column.key || declared.cell !== column.cell || column.cell === "choice" && declared.cell === "choice" && JSON.stringify(column.choices) !== JSON.stringify(declared.choices);
702
+ })) throw new TableBindingError("STRUCTURE", "Header metadata must match the supplied schema");
703
+ }
704
+ #reconcile() {
705
+ this.#validateSchema();
706
+ const elements = Array.from(this.#rows).filter(([, node]) => !this.#visible.has(node) || node.parentElement === this.#body).map(([, node]) => node);
707
+ for (const node of this.#body.rows) if (!elements.includes(node) && !Array.from(this.#details.values()).includes(node)) elements.push(node);
708
+ const rows = readTableRows(this.#document.schema, elements);
709
+ this.#records = [];
710
+ const keys = new Set(this.#keys(rows));
711
+ const additions = [];
712
+ const replacements = [];
713
+ for (const row of rows) {
714
+ const key = extractKey(this.#document.schema, row);
715
+ if (key !== void 0 && this.#document.rows.row(key) === void 0) additions.push(row);
716
+ else replacements.push(row);
717
+ }
718
+ this.#document.rows.replace(replacements);
719
+ if (this.destroyed) return;
720
+ this.#document.rows.add(additions);
721
+ if (this.destroyed) return;
722
+ for (const key of this.#rows.keys()) if (!keys.has(key)) {
723
+ this.#document.rows.remove(key);
724
+ if (this.destroyed) return;
725
+ }
726
+ for (const [index, row] of rows.entries()) {
727
+ const key = extractKey(this.#document.schema, row);
728
+ const node = elements[index];
729
+ if (key !== void 0 && node !== void 0) this.#rows.set(key, node);
730
+ }
731
+ }
732
+ #render() {
733
+ const active = this.#element.ownerDocument.activeElement;
734
+ const focused = active instanceof HTMLElement && [...this.#rows.values(), ...this.#details.values()].some((row) => row.contains(active)) ? active : void 0;
735
+ const stored = this.#document.rows.rows();
736
+ const keys = new Set(this.#keys(stored));
737
+ for (const [key, node] of this.#rows) if (!keys.has(key)) {
738
+ this.#place(node);
739
+ this.#release(node);
740
+ const detail = this.#details.get(key);
741
+ if (detail !== void 0) this.#place(detail);
742
+ this.#rows.delete(key);
743
+ this.#values.delete(key);
744
+ this.#details.delete(key);
745
+ this.#detailValues.delete(key);
746
+ }
747
+ const view = this.#document.view;
748
+ const ordered = [];
749
+ for (const row of view) {
750
+ const key = extractKey(this.#document.schema, row);
751
+ if (key === void 0) continue;
752
+ let node = this.#rows.get(key);
753
+ if (node === void 0) {
754
+ if (this.#options.row === void 0) throw new TableBindingError("STRUCTURE", "An authored document row has no native row");
755
+ node = this.#element.ownerDocument.createElement("tr");
756
+ node.setAttribute("data-table-key", key);
757
+ this.#rows.set(key, node);
758
+ }
759
+ if (this.#options.row !== void 0 && !matchesTableRow(this.#values.get(key), row)) {
760
+ this.#options.row(node, row);
761
+ if (this.destroyed) return;
762
+ this.#values.set(key, row);
763
+ }
764
+ ordered.push(node);
765
+ const detail = this.#detail(key, row);
766
+ if (this.destroyed) return;
767
+ if (detail !== void 0) ordered.push(detail);
768
+ this.#controls(node, key, detail);
769
+ }
770
+ for (const node of [...this.#rows.values(), ...this.#details.values()]) if (!ordered.includes(node) && node.parentElement === this.#body) this.#place(node);
771
+ for (const [index, node] of ordered.entries()) if (this.#body.rows[index] !== node) this.#place(node, this.#body.rows[index] ?? null);
772
+ this.#visible.clear();
773
+ for (const node of this.#rows.values()) if (node.parentElement === this.#body) this.#visible.add(node);
774
+ this.#headers();
775
+ if (this.destroyed) return;
776
+ this.#pages(view);
777
+ if (this.destroyed) return;
778
+ this.#pager();
779
+ if (this.destroyed) return;
780
+ this.#restoreFocus(focused, true);
781
+ this.#observe();
782
+ if (this.#records.length > 0) this.#enqueue();
783
+ }
784
+ #restoreFocus(focused, hidden = false) {
785
+ if (focused !== void 0 && this.#element.ownerDocument.activeElement !== focused && (this.#element.ownerDocument.activeElement === this.#element.ownerDocument.body || this.#element.ownerDocument.activeElement === null)) {
786
+ if (focused.isConnected) focused.focus({ preventScroll: true });
787
+ else if (hidden) this.#focusTable();
788
+ }
789
+ }
790
+ #focusTable() {
791
+ this.#attribute(this.#element, "tabindex", "-1");
792
+ if (!this.#element.hasAttribute("aria-label") && !this.#element.hasAttribute("aria-labelledby") && this.#element.caption === null) this.#attribute(this.#element, "aria-label", this.#document.schema.label ?? "Table");
793
+ this.#element.focus({ preventScroll: true });
794
+ }
795
+ #detail(key, row) {
796
+ if (this.#options.detail === void 0 || !this.#document.expansion.keys.has(key)) return void 0;
797
+ let detail = this.#details.get(key);
798
+ if (detail === void 0) {
799
+ detail = this.#element.ownerDocument.createElement("tr");
800
+ detail.id = `table-detail-${crypto.randomUUID()}`;
801
+ detail.insertCell().colSpan = Math.max(1, this.#rows.get(key)?.cells.length ?? this.#document.schema.columns.length);
802
+ this.#details.set(key, detail);
803
+ }
804
+ if (!matchesTableRow(this.#detailValues.get(key), row)) {
805
+ this.#options.detail(detail, row);
806
+ if (!this.destroyed) this.#detailValues.set(key, row);
807
+ }
808
+ return detail;
809
+ }
810
+ #place(node, before) {
811
+ this.#records.push(...this.#observer.takeRecords());
812
+ this.#observer.disconnect();
813
+ if (before === void 0) node.remove();
814
+ else if (typeof this.#body.moveBefore === "function" && node.isConnected && this.#body.isConnected) this.#body.moveBefore(node, before);
815
+ else this.#body.insertBefore(node, before);
816
+ this.#observe();
817
+ }
818
+ #attribute(element, name, value) {
819
+ let attributes = this.#attributes.get(element);
820
+ if (attributes === void 0) {
821
+ attributes = /* @__PURE__ */ new Map();
822
+ this.#attributes.set(element, attributes);
823
+ }
824
+ const entry = attributes.get(name);
825
+ if (entry === void 0) attributes.set(name, {
826
+ original: element.getAttribute(name),
827
+ value
828
+ });
829
+ else entry.value = value;
830
+ if (value === null) element.removeAttribute(name);
831
+ else if (element.getAttribute(name) !== value) element.setAttribute(name, value);
832
+ }
833
+ #release(root) {
834
+ for (const [element, attributes] of this.#attributes) if (root.contains(element)) {
835
+ for (const [name, entry] of attributes) if (element.getAttribute(name) === entry.value) {
836
+ if (entry.original === null) element.removeAttribute(name);
837
+ else element.setAttribute(name, entry.original);
838
+ }
839
+ this.#attributes.delete(element);
840
+ }
841
+ for (const [input, state] of this.#checks) if (root.contains(input)) {
842
+ if (input.checked === state.value) input.checked = state.checked;
843
+ if (input.indeterminate === state.mixed) input.indeterminate = state.indeterminate;
844
+ this.#checks.delete(input);
845
+ }
846
+ }
847
+ #check(input, value, mixed) {
848
+ const state = this.#checks.get(input);
849
+ if (state === void 0) this.#checks.set(input, {
850
+ checked: input.checked,
851
+ indeterminate: input.indeterminate,
852
+ value,
853
+ mixed
854
+ });
855
+ else {
856
+ state.value = value;
857
+ state.mixed = mixed;
858
+ }
859
+ input.checked = value;
860
+ input.indeterminate = mixed;
861
+ }
862
+ #controls(node, key, detail) {
863
+ const table = node.closest("table");
864
+ for (const input of node.querySelectorAll("input[type=\"checkbox\"][data-table-select]")) if (input instanceof HTMLInputElement && input.closest("table") === table) this.#check(input, this.#document.selection.keys.has(key), false);
865
+ for (const button of node.querySelectorAll("button[data-table-expand]:not([data-table-expand=\"page\"])")) if (button.closest("table") === table) {
866
+ this.#attribute(button, "aria-expanded", String(this.#document.expansion.keys.has(key)));
867
+ this.#attribute(button, "aria-controls", detail?.id ?? null);
868
+ }
869
+ }
870
+ #headers() {
871
+ const orders = this.#document.sort.orders();
872
+ for (const button of this.#element.tHead?.querySelectorAll("button[data-table-sort]") ?? []) {
873
+ if (button.closest("table") !== this.#element) continue;
874
+ const header = button.closest("th");
875
+ if (header === null) continue;
876
+ const column = button.getAttribute("data-table-sort");
877
+ const index = orders.findIndex((order) => order.column === column);
878
+ const order = orders[index];
879
+ const description = order === void 0 ? null : this.#options.describe?.sort?.({
880
+ column: order.column,
881
+ direction: order.direction,
882
+ priority: index + 1,
883
+ count: orders.length
884
+ }) ?? `Sort priority ${index + 1} of ${orders.length}, ${order.direction}`;
885
+ if (this.destroyed) return;
886
+ this.#attribute(header, "aria-sort", index === 0 && order !== void 0 ? order.direction : null);
887
+ this.#attribute(button, "aria-description", description);
888
+ }
889
+ }
890
+ #pages(view) {
891
+ const keys = this.#keys(view);
892
+ for (const button of this.#scoped("button[data-table-expand=\"page\"]")) {
893
+ this.#attribute(button, "aria-expanded", String(keys.length > 0 && keys.every((key) => this.#document.expansion.keys.has(key))));
894
+ if (button instanceof HTMLButtonElement) this.#disable(button, keys.length === 0);
895
+ if (this.destroyed) return;
896
+ }
897
+ const selected = view.filter((row) => {
898
+ const key = extractKey(this.#document.schema, row);
899
+ return key !== void 0 && this.#document.selection.keys.has(key);
900
+ }).length;
901
+ for (const input of this.#scope.querySelectorAll("input[type=\"checkbox\"][data-table-page]")) if (input instanceof HTMLInputElement && (input.closest("table") === null || input.closest("table") === this.#element)) this.#check(input, view.length > 0 && selected === view.length, selected > 0 && selected < view.length);
902
+ }
903
+ #scoped(selector) {
904
+ return Array.from(this.#scope.querySelectorAll(selector)).filter((control) => {
905
+ const table = control.closest("table");
906
+ return table === null || table === this.#element;
907
+ });
908
+ }
909
+ #disable(button, disabled, sibling) {
910
+ if (disabled && this.#element.ownerDocument.activeElement === button) {
911
+ if (sibling !== void 0) sibling.focus({ preventScroll: true });
912
+ else this.#focusTable();
913
+ }
914
+ if (!this.destroyed) this.#attribute(button, "disabled", disabled ? "" : null);
915
+ }
916
+ #value(control, value) {
917
+ const state = this.#controlValues.get(control);
918
+ if (state === void 0) this.#controlValues.set(control, {
919
+ original: control.value,
920
+ value
921
+ });
922
+ else state.value = value;
923
+ if (control.value !== value) control.value = value;
924
+ }
925
+ #readRange() {
926
+ const pagination = this.#document.pagination;
927
+ const length = this.#document.view.length;
928
+ return {
929
+ first: length === 0 ? 0 : pagination.offset + 1,
930
+ last: length === 0 ? 0 : pagination.offset + length,
931
+ count: this.#document.count,
932
+ page: pagination.page,
933
+ pages: pagination.count,
934
+ limit: pagination.limit
935
+ };
936
+ }
937
+ #pager() {
938
+ const range = this.#readRange();
939
+ const buttons = this.#scoped("button[data-table-move=\"previous\"], button[data-table-move=\"next\"]").filter((button) => button instanceof HTMLButtonElement);
940
+ const enabled = buttons.filter((button) => button.getAttribute("data-table-move") === "previous" ? range.page > 1 : range.page < range.pages);
941
+ for (const button of enabled) this.#disable(button, false);
942
+ for (const button of buttons) {
943
+ if (!enabled.includes(button)) this.#disable(button, true, enabled.find((sibling) => sibling.getAttribute("data-table-move") !== button.getAttribute("data-table-move")));
944
+ if (this.destroyed) return;
945
+ }
946
+ for (const select of this.#scoped("select[data-table-limit]")) if (select instanceof HTMLSelectElement) this.#value(select, range.limit === void 0 ? "" : String(range.limit));
947
+ const signature = JSON.stringify(range);
948
+ if (signature === this.#range) return;
949
+ const outputs = this.#scoped("output[data-table-range]").filter((output) => output instanceof HTMLOutputElement);
950
+ const description = this.#options.describe?.range?.(range) ?? `${range.first} to ${range.last} of ${range.count}`;
951
+ if (this.destroyed) return;
952
+ if (signature !== JSON.stringify(this.#readRange())) {
953
+ this.#enqueue();
954
+ return;
955
+ }
956
+ this.#range = signature;
957
+ if (this.#announcement !== void 0) clearTimeout(this.#announcement);
958
+ for (const output of outputs) this.#value(output, "");
959
+ this.#announcement = setTimeout(() => {
960
+ this.#announcement = void 0;
961
+ if (this.destroyed) return;
962
+ if (signature !== JSON.stringify(this.#readRange())) {
963
+ this.#range = void 0;
964
+ this.#enqueue();
965
+ return;
966
+ }
967
+ for (const output of outputs) if (this.#scoped("output[data-table-range]").includes(output)) this.#value(output, description);
968
+ }, 0);
969
+ }
970
+ #click(event) {
971
+ if (!(event.target instanceof Element) || this.destroyed || this.#requesting) return;
972
+ const button = event.target.closest("button");
973
+ if (!(button instanceof HTMLButtonElement) || button.disabled || button.closest("table") !== null && button.closest("table") !== this.#element) return;
974
+ const outcome = attempt(() => {
975
+ const move = button.getAttribute("data-table-move");
976
+ if (move === "previous" || move === "next") this.#document.pagination.move(this.#document.pagination.page + (move === "previous" ? -1 : 1));
977
+ else if (button.getAttribute("data-table-expand") === "page") {
978
+ const keys = this.#keys(this.#document.view);
979
+ if (keys.length > 0 && keys.every((key) => this.#document.expansion.keys.has(key))) this.#document.expansion.clear(keys);
980
+ else this.#document.expansion.expand(keys);
981
+ } else if (button.hasAttribute("data-table-sort") && this.#element.tHead?.contains(button)) {
982
+ const column = button.getAttribute("data-table-sort");
983
+ if (column === null || !this.#document.schema.columns.some((entry) => entry.key === column)) throw new TableBindingError("STRUCTURE", "The sort button names no column");
984
+ const direction = this.#document.sort.order(column)?.direction;
985
+ const orders = direction === "descending" ? [] : [{
986
+ column,
987
+ direction: direction === void 0 ? "ascending" : "descending"
988
+ }];
989
+ const request = new CustomEvent("table-sort", {
990
+ cancelable: true,
991
+ detail: orders
992
+ });
993
+ this.#requesting = true;
994
+ this.#emitter.emit("sort", request);
995
+ this.#requesting = false;
996
+ if (request.defaultPrevented || this.destroyed) return;
997
+ this.#document.sort.remove();
998
+ if (!this.destroyed) this.#document.sort.set(orders);
999
+ } else if (button.hasAttribute("data-table-expand")) {
1000
+ const row = button.closest("tr");
1001
+ const key = Array.from(this.#rows).find(([, node]) => node === row)?.[0];
1002
+ if (key !== void 0) this.#document.expansion.toggle(key);
1003
+ }
1004
+ });
1005
+ if (!outcome.success) this.#emitter.emit("error", outcome.error);
1006
+ }
1007
+ #change(event) {
1008
+ const input = event.target;
1009
+ if (input instanceof HTMLSelectElement && input.hasAttribute("data-table-limit")) {
1010
+ if (input.disabled || this.destroyed || !this.#scoped("select[data-table-limit]").includes(input)) return;
1011
+ const outcome = attempt(() => {
1012
+ const limit = input.value === "" ? void 0 : parseNumber(input.value);
1013
+ if (input.value !== "" && (limit === void 0 || limit <= 0 || !Number.isInteger(limit))) throw new TableBindingError("STRUCTURE", "The page limit must be a positive integer or empty");
1014
+ this.#document.pagination.resize(limit);
1015
+ });
1016
+ if (!outcome.success) this.#emitter.emit("error", outcome.error);
1017
+ return;
1018
+ }
1019
+ if (!(input instanceof HTMLInputElement) || input.type !== "checkbox" || input.disabled || this.destroyed) return;
1020
+ const table = input.closest("table");
1021
+ if (table !== null && table !== this.#element) return;
1022
+ const outcome = attempt(() => {
1023
+ if (input.hasAttribute("data-table-page")) {
1024
+ const keys = this.#keys(this.#document.view);
1025
+ if (input.checked) this.#document.selection.select(keys);
1026
+ else this.#document.selection.clear(keys);
1027
+ } else if (input.hasAttribute("data-table-select") && table === this.#element) {
1028
+ const key = Array.from(this.#rows).find(([, node]) => node === input.closest("tr"))?.[0];
1029
+ if (key !== void 0) {
1030
+ if (input.checked) this.#document.selection.select(key);
1031
+ else this.#document.selection.clear(key);
1032
+ }
1033
+ }
1034
+ });
1035
+ if (!outcome.success) this.#emitter.emit("error", outcome.error);
1036
+ }
1037
+ #keys(rows) {
1038
+ return rows.flatMap((row) => {
1039
+ const key = extractKey(this.#document.schema, row);
1040
+ return key === void 0 ? [] : [key];
1041
+ });
1042
+ }
1043
+ };
1044
+ //#endregion
1045
+ //#region src/browser/factories.ts
1046
+ /** Creates a native table binding with one retained row owner.
1047
+ * @param element - The native table to bind.
1048
+ * @param document - The borrowed core document.
1049
+ * @param options - Renderers, scoped controls, and listeners.
1050
+ * @returns The native binding.
1051
+ */
1052
+ function createTableBinding(element, document, options) {
1053
+ return new TableBinding(element, document, options);
1054
+ }
1055
+ //#endregion
1056
+ export { TableBinding, TableBindingError, createTableBinding, isTableBindingError, readTable, readTableRows, readTableSchema };
1057
+
1058
+ //# sourceMappingURL=index.js.map