UNPKG

verstak

Version:
281 lines (280 loc) 14.9 kB
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), };