UNPKG

alinea

Version:

[![npm](https://img.shields.io/npm/v/alinea.svg)](https://npmjs.org/package/alinea) [![install size](https://packagephobia.com/badge?p=alinea)](https://packagephobia.com/result?p=alinea)

231 lines (229 loc) 6.71 kB
import { YMap, YMapEvent } from "../../chunks/chunk-OYP4EJOA.js"; import "../../chunks/chunk-O6EXLFU2.js"; import "../../chunks/chunk-U5RRZUYZ.js"; // src/core/shape/ListShape.ts import { Hint } from "../Hint.js"; import { createId } from "../Id.js"; import { generateKeyBetween } from "../util/FractionalIndexing.js"; import { RecordShape } from "./RecordShape.js"; import { ScalarShape } from "./ScalarShape.js"; function sort(a, b) { if (a.index < b.index) return -1; if (a.index > b.index) return 1; return 0; } var ListShape = class { constructor(label, shapes, initialValue, postProcess) { this.label = label; this.shapes = shapes; this.initialValue = initialValue; this.postProcess = postProcess; this.values = Object.fromEntries( Object.entries(shapes).map(([key, type]) => { return [ key, new RecordShape(label, { id: new ScalarShape("Id"), index: new ScalarShape("Index"), type: new ScalarShape("Type"), ...type.properties }) ]; }) ); } values; innerTypes(parents) { return Object.entries(this.shapes).flatMap(([name, shape]) => { const info = { name, shape, parents }; const inner = shape.innerTypes(parents.concat(name)); if (Hint.isDefinitionName(name)) return [info, ...inner]; return inner; }); } create() { return this.initialValue ?? []; } typeOfChild(yValue, child) { const row = yValue.get(child); const type = row && row.get("type"); const value = type && this.values[type]; if (value) return value; throw new Error(`Could not determine type of child "${child}"`); } toY(value) { const map = new YMap(); const rows = Array.isArray(value) ? value : []; let currentIndex = null; for (const row of rows) { const id = row.id; const type = row.type; const valueType = this.values[type]; if (!id || !type || !valueType) continue; currentIndex = generateKeyBetween(currentIndex, null); map.set(id, valueType.toY({ ...row, index: currentIndex })); } return map; } fromY(map) { const rows = []; if (!map || typeof map.keys !== "function") return rows; for (const key of map.keys()) { const row = map.get(key); if (!row || typeof row.get !== "function") continue; const type = row.get("type"); const rowType = this.values[type]; if (rowType) rows.push(rowType.fromY(row)); } rows.sort(sort); return rows; } applyY(value, parent, key) { if (!Array.isArray(value)) return; const current = parent.get(key); if (!current) return void parent.set(key, this.toY(value)); const currentKeys = new Set(current.keys()); const valueKeys = new Set(value.map((row) => row.id)); const removed = [...currentKeys].filter((key2) => !valueKeys.has(key2)); const added = [...valueKeys].filter((key2) => !currentKeys.has(key2)); const changed = [...valueKeys].filter((key2) => currentKeys.has(key2)); for (const id of removed) current.delete(id); for (const id of added) { const row = value.find((row2) => row2.id === id); if (!row) continue; const type = row.type; const rowType = this.values[type]; if (!rowType) continue; current.set(id, rowType.toY(row)); } for (const id of changed) { const row = value.find((row2) => row2.id === id); if (!row) continue; const type = row.type; const currentRow = current.get(id); if (!currentRow) continue; const currentType = currentRow.get("type"); if (currentType !== type) { current.delete(id); current.set(id, this.values[type].toY(row)); continue; } const rowType = this.values[type]; if (!rowType) continue; rowType.applyY(row, current, id); } } init(parent, key) { if (!parent.has(key)) parent.set(key, this.toY(this.create())); } watch(parent, key) { const record = parent.has(key) ? parent.get(key) : parent.set(key, new YMap()); return (fun) => { function w(events, transaction) { for (const event of events) { if (event.target === record) fun(); if (event instanceof YMapEvent && event.keysChanged.has("index")) fun(); } } record.observeDeep(w); return () => { record.unobserveDeep(w); }; }; } mutator(parent, key, readOnly) { const res = { readOnly, replace: (id, row) => { if (readOnly) return; const record = parent.get(key); const rows = this.fromY(record); const index = rows.findIndex((r) => r.id === id); res.remove(id); res.push(row, index); }, push: (row, insertAt) => { if (readOnly) return; const type = row.type; const shape = this.values[type]; const record = parent.get(key); const rows = this.fromY(record); const id = createId(); const before = insertAt === void 0 ? rows.length - 1 : insertAt - 1; const after = before + 1; const keyA = rows[before]?.index || null; const keyB = rows[after]?.index || null; const item = shape.toY({ ...shape.create(), ...row, id, index: generateKeyBetween(keyA, keyB) }); record.set(id, item); }, remove(id) { if (readOnly) return; const record = parent.get(key); record.delete(id); }, move: (oldIndex, newIndex) => { if (readOnly) return; const record = parent.get(key); const rows = this.fromY(record); const from = rows[oldIndex]; const into = rows.filter((row2) => row2.id !== from.id); const prev = into[newIndex - 1]; const next = into[newIndex]; const a = prev?.index || null; const b = next?.index || null; const index = generateKeyBetween(a, b); const row = record.get(from.id); row.set("index", index); } }; return res; } async applyLinks(value, loader) { const tasks = []; if (!Array.isArray(value)) return; for (const row of value) { const type = row.type; const shape = this.values[type]; if (shape) tasks.push(shape.applyLinks(row, loader)); } await Promise.all(tasks); if (this.postProcess) await this.postProcess(value, loader); } }; export { ListShape };