alinea
Version:
[](https://npmjs.org/package/alinea) [](https://packagephobia.com/result?p=alinea)
231 lines (229 loc) • 6.71 kB
JavaScript
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
};