verstak
Version:
Verstak - Front-End Library
281 lines (280 loc) • 14.9 kB
JavaScript
import { ReactiveTreeNode, Mode, runNonReactively } from "reactronic";
import { ElKind, Direction } from "./El.js";
import { clamp } from "./ElUtils.js";
import { Constants, CursorCommandDriver, ElDriver, ElLayoutInfo, InitialElLayoutInfo } from "./ElDriver.js";
import { getPrioritiesForEmptySpaceDistribution, getPrioritiesForSizeChanging, relayout, relayoutUsingSplitter } from "./SplitViewMath.js";
import { Axis, BodyFontSize, Dimension, toPx } from "./Sizes.js";
import { HtmlDriver, StaticDriver } from "./WebDriver.js";
export function Window(scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis) {
const driver = new StaticDriver(global.document.body, "Page", false, el => el.kind = ElKind.division);
return ReactiveTreeNode.declare(driver, scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis);
}
export function Division(scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis) {
return ReactiveTreeNode.declare(Drivers.division, scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis);
}
export function Table(scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis) {
return ReactiveTreeNode.declare(Drivers.table, scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis);
}
export function row(builder, shiftCursorDown) {
rowBreak(shiftCursorDown);
builder === null || builder === void 0 ? void 0 : builder();
}
export function Splitter(scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis) {
return ReactiveTreeNode.declare(Drivers.splitter, scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis);
}
export function rowBreak(shiftCursorDown) {
ReactiveTreeNode.declare(Drivers.partition);
}
export function declareSplitter(index, splitViewNode) {
const key = `splitter-${index}`;
return (Splitter({
key,
mode: Mode.autonomous,
preparation: el => el.native.className = `splitter ${key}`,
script: b => {
const e = b.native;
const model = b.model;
const dataForSensor = e.dataForSensor;
dataForSensor.draggable = key;
dataForSensor.drag = key;
Fragment(() => {
var _a, _b, _c, _d;
const pointer = e.sensors.pointer;
if (pointer.dragSource === key) {
if (pointer.dragStarted) {
if (pointer.draggingOver) {
pointer.dropAllowed = true;
const initialSizesPx = pointer.getData();
const deltaPx = Math.floor(splitViewNode.element.splitView === Direction.horizontal
? pointer.positionX - pointer.startX : pointer.positionY - pointer.startY);
const clonedSizesPx = [];
for (const item of initialSizesPx) {
clonedSizesPx.push({ node: item.node, sizePx: item.sizePx });
}
runNonReactively(() => relayoutUsingSplitter(splitViewNode, deltaPx, index, clonedSizesPx));
if (pointer.dropped) {
(_a = model === null || model === void 0 ? void 0 : model.droppedAction) === null || _a === void 0 ? void 0 : _a.call(model, pointer);
}
}
else {
e.setAttribute("rx-dragging", "true");
const initialSizesPx = [];
for (const item of splitViewNode.children.items()) {
const child = item.instance;
if (isSplitViewPartition(child.driver)) {
const sizePx = (_c = (_b = child.element.layoutInfo) === null || _b === void 0 ? void 0 : _b.effectiveSizePx) !== null && _c !== void 0 ? _c : 0;
initialSizesPx.push({ node: child, sizePx });
}
}
pointer.setData(initialSizesPx);
}
if (pointer.dragFinished) {
(_d = model === null || model === void 0 ? void 0 : model.dragFinishedAction) === null || _d === void 0 ? void 0 : _d.call(model, pointer);
e.setAttribute("rx-dragging", "false");
}
}
}
});
},
}));
}
export function cursor(place) {
ReactiveTreeNode.declare(Drivers.cursor, {
script: el => {
el.place = place;
},
});
}
export function JustText(content, formatted, declaration) {
return ReactiveTreeNode.declare(Drivers.text, ReactiveTreeNode.withBasis(declaration, {
script: el => {
if (formatted)
el.native.innerHTML = content;
else
el.native.innerText = content;
},
}));
}
export function Group(scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis) {
return ReactiveTreeNode.declare(Drivers.group, scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis);
}
export function Fragment(script) {
return PseudoElement({ mode: Mode.autonomous, script });
}
export function PseudoElement(scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis) {
return ReactiveTreeNode.declare(Drivers.pseudo, scriptOrDeclaration, scriptAsync, key, mode, preparation, preparationAsync, finalization, triggers, basis);
}
export class DivisionDriver extends HtmlDriver {
runScript(node) {
rowBreak();
const el = node.element;
const result = super.runScript(node);
if (el.splitView !== undefined) {
if (el.layoutInfo === undefined)
el.layoutInfo = new ElLayoutInfo(InitialElLayoutInfo);
const layoutInfo = el.layoutInfo;
layoutInfo.isUpdateFinished = false;
Fragment(h => {
const native = el.native;
const resize = native.sensors.resize;
for (const x of resize.resizedElements) {
const borderBoxPx = x.borderBoxSize[0];
const contentBoxPx = x.contentBoxSize[0];
layoutInfo.borderSizeXpx = borderBoxPx.inlineSize;
layoutInfo.borderSizeYpx = borderBoxPx.blockSize;
layoutInfo.contentSizeXpx = contentBoxPx.inlineSize;
layoutInfo.contentSizeYpx = contentBoxPx.blockSize;
}
});
const relayoutEl = PseudoElement({
mode: Mode.autonomous,
script: () => {
const native = el.native;
const isHorizontal = el.splitView === Direction.horizontal;
if (layoutInfo.isUpdateFinished) {
const surroundingXpx = layoutInfo.borderSizeXpx - layoutInfo.contentSizeXpx;
const surroundingYpx = layoutInfo.borderSizeYpx - layoutInfo.contentSizeYpx;
let i = 0;
const preferred = [];
const sizesPx = [];
for (const child of node.children.items()) {
const partEl = child.instance.element;
if (isSplitViewPartition(child.instance.driver) && partEl !== undefined) {
const size = isHorizontal ? partEl.width : partEl.height;
const options = {
axis: isHorizontal ? Axis.X : Axis.Y, lineSizePx: BodyFontSize, fontSizePx: BodyFontSize,
containerSizeXpx: native.scrollWidth - surroundingXpx, containerSizeYpx: native.scrollHeight - surroundingYpx,
};
const minPx = size.min ? toPx(Dimension.parse(size.min), options) : 0;
let maxPx = size.max ? toPx(Dimension.parse(size.max), options) : Number.POSITIVE_INFINITY;
maxPx = Math.max(minPx, maxPx);
if (partEl.layoutInfo === undefined)
partEl.layoutInfo = new ElLayoutInfo(InitialElLayoutInfo);
if (isHorizontal)
partEl.widthPx = { minPx, maxPx };
else
partEl.heightPx = { minPx, maxPx };
const preferredPx = size.preferred ? toPx(Dimension.parse(size.preferred), options) : 0;
if (preferredPx > 0) {
partEl.layoutInfo.effectiveSizePx = preferredPx;
size.preferred = undefined;
preferred.push(i);
}
const sizePx = partEl.layoutInfo.effectiveSizePx = clamp(partEl.layoutInfo.effectiveSizePx, minPx, maxPx);
sizesPx.push({ node: child.instance, sizePx });
i++;
}
}
const priorities = preferred.length > 0
? getPrioritiesForSizeChanging(isHorizontal, node.children, preferred)
: getPrioritiesForEmptySpaceDistribution(isHorizontal, node.children);
runNonReactively(() => relayout(node, priorities.resizable, priorities.manuallyResizable, sizesPx));
}
},
});
ReactiveTreeNode.runNestedNodeScriptsThenDo(() => {
layoutInfo.isUpdateFinished = true;
ReactiveTreeNode.triggerScriptRun(relayoutEl, { stamp: node.stamp });
});
}
return result;
}
declareChild(ownerNode, childDriver, childDeclaration, childBasis) {
var _a;
let result = undefined;
const el = ownerNode.element;
if (el.splitView !== undefined) {
if (isSplitViewPartition(childDriver)) {
let partCount = 0;
for (const child of ownerNode.children.items()) {
if (isSplitViewPartition(child.instance.driver))
partCount++;
}
const isHorizontal = el.splitView === Direction.horizontal;
if (childDeclaration !== undefined) {
if (childDeclaration.triggers === undefined)
childDeclaration.triggers = {};
Object.defineProperty(childDeclaration.triggers, "index", { value: partCount });
overrideMethod(childDeclaration, "script", el => {
if (isHorizontal)
el.style.gridColumn = `${partCount + 1}`;
else
el.style.gridRow = `${partCount + 1}`;
});
}
if (partCount > 0)
declareSplitter(partCount - 1, ownerNode);
}
}
else {
if (childDriver.isPartition) {
const last = ownerNode.children.lastMergedItem();
if (((_a = last === null || last === void 0 ? void 0 : last.instance) === null || _a === void 0 ? void 0 : _a.driver) === childDriver)
result = last;
}
}
return result;
}
}
export function isSplitViewPartition(childDriver) {
return !childDriver.isPartition && childDriver !== Drivers.splitter && childDriver !== Drivers.pseudo;
}
function overrideMethod(declaration, method, func) {
const baseScript = declaration[method];
declaration[method] = baseScript !== undefined
? (el, base) => { baseScript(el, base); func(el); }
: (el, base) => { base(); func(el); };
}
export class PartitionDriver extends HtmlDriver {
runScript(node) {
const result = super.runScript(node);
const ownerEl = node.owner.element;
if (ownerEl.sealed !== undefined) {
node.element.style.flexGrow = "1";
ReactiveTreeNode.declare(Drivers.wrapper, {
script: el => {
const ownerEl = el.node.owner.owner.element;
if (ownerEl.splitView !== undefined) {
el.style.display = "grid";
el.style.flexDirection = "";
}
else {
if (ownerEl.isTable) {
el.style.display = "contents";
el.style.flexDirection = "";
}
else {
el.style.display = "flex";
el.style.flexDirection = "row";
}
}
el.style.position = "absolute";
el.style.inset = "0";
el.style.overflow = "auto";
el.style.gap = "inherit";
},
});
}
return result;
}
provideHost(node) {
let host;
const ownerEl = node.owner.element;
if (ownerEl.sealed !== undefined)
host = node.children.firstMergedItem().instance;
else
host = node;
return host;
}
}
export const Drivers = {
division: new DivisionDriver(Constants.element, false, el => el.kind = ElKind.division),
table: new HtmlDriver(Constants.element, false, el => el.kind = ElKind.table),
text: new HtmlDriver(Constants.element, false, el => el.kind = ElKind.text),
group: new HtmlDriver(Constants.group, false, el => el.kind = ElKind.group),
partition: new PartitionDriver(Constants.partition, true, el => el.kind = ElKind.partition),
wrapper: new HtmlDriver(Constants.wrapper, false, el => el.kind = ElKind.native),
splitter: new HtmlDriver(Constants.splitter, false, el => el.kind = ElKind.splitter),
cursor: new CursorCommandDriver(),
pseudo: new ElDriver("pseudo", false, el => el.kind = ElKind.group),
};