UNPKG

lume

Version:

Build next-level interactive web applications.

400 lines 18 kB
/** * This Source Code is licensed under the MIT license. If a copy of the * MIT-license was not distributed with this file, You can obtain one at: * http://opensource.org/licenses/mit-license.html. * * @author: Hein Rutjes (IjzerenHein) and Joe Pea (trusktr) * @license MIT * @copyright Gloey Apps, 2015 * @copyright Joe Pea, 2018 */ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; // TODO: // - Use MutationObserver to watch for className changes and update laid-out nodes. // - Perhaps once we get to the ShadowDOM stuff we can use slots instead. It'll // be more powerful, letting us distribute any number of nodes into each layout // slot. Also it eliminated edge cases that we'll have to handle with the // className approach. // - Make an <lume-visual-format> element that can contain visual format code to // re-use in multiple layouts. // - Allow visual-format to be fetch by path (like img src attribute). import AutoLayout from '@lume/autolayout'; import { attribute, element } from '@lume/element'; import { Element3D } from '../core/Element3D.js'; import { Motor } from '../core/Motor.js'; import { autoDefineElements } from '../LumeConfig.js'; export { AutoLayout }; /** * An Element3D that lays children out based on an Apple AutoLayout VFL layout * description. */ let Autolayout = (() => { let _classDecorators = [element('lume-autolayout', autoDefineElements)]; let _classDescriptor; let _classExtraInitializers = []; let _classThis; let _classSuper = Element3D; let _visualFormat_decorators; let _visualFormat_initializers = []; let _visualFormat_extraInitializers = []; var Autolayout = class extends _classSuper { static { _classThis = this; } static { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0; _visualFormat_decorators = [attribute]; __esDecorate(null, null, _visualFormat_decorators, { kind: "field", name: "visualFormat", static: false, private: false, access: { has: obj => "visualFormat" in obj, get: obj => obj.visualFormat, set: (obj, value) => { obj.visualFormat = value; } }, metadata: _metadata }, _visualFormat_initializers, _visualFormat_extraInitializers); __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); Autolayout = _classThis = _classDescriptor.value; if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); } static DEFAULT_PARSE_OPTIONS = { extended: true, strict: false, }; visualFormat = __runInitializers(this, _visualFormat_initializers, '' /** * Constructor * * @param {Object} [options] options to set. * @param {String|Array} [options.visualFormat] String or array of strings containing VFL. * @param {Object} [options.layoutOptions] Options such as viewport, spacing, etc... TODO make this a reactive property. * @return {Autolayout} this */ ); /** * Constructor * * @param {Object} [options] options to set. * @param {String|Array} [options.visualFormat] String or array of strings containing VFL. * @param {Object} [options.layoutOptions] Options such as viewport, spacing, etc... TODO make this a reactive property. * @return {Autolayout} this */ constructor(options) { super(); // PORTED { this.on('sizechange', this.#layout); this.on('reflow', this.#layout); // } // TODO use Settable.set instead. if (options) { if (options.visualFormat) { this.setVisualFormat(options.visualFormat); } if (options.layoutOptions) { this.setLayoutOptions(options.layoutOptions); } } } connectedCallback() { super.connectedCallback(); this.createEffect(() => { this.setVisualFormat(this.visualFormat || ''); }); } #autoLayoutView = __runInitializers(this, _visualFormat_extraInitializers); childConnectedCallback(child) { // @prod-prune if (!(child instanceof Element3D)) throw new Error('Child elements of Autolayout must be instances of LUME.Element3D.'); super.childConnectedCallback(child); if (!this.#autoLayoutView) return; this.#checkNodes(); } childDisconnectedCallback(child) { // @prod-prune if (!(child instanceof Element3D)) throw new Error('Child elements of Autolayout must be instances of LUME.Element3D.'); super.childDisconnectedCallback(child); if (!this.#autoLayoutView) return; const _idToNode = this.#idToNode; for (const id in _idToNode) { if (_idToNode[id] === child) { delete _idToNode[id]; break; } } this.#checkNodes(); } /** * Forces a reflow of the layout. * * @return {Autolayout} this */ reflowLayout() { if (!this.#reflowLayout) { this.#reflowLayout = true; Motor.once(() => this.emit('reflow')); // PORTED } return this; } /** * Sets the visual-format string. * * @param {String|Array} visualFormat String or array of strings containing VFL. * @param {Object} [parseOptions] Specify to override the parse options for the VFL. * @return {Autolayout} this */ setVisualFormat(visualFormat, parseOptions) { // @ts-ignore: TODO, _visualFormat is not used anywhere, but it works??? this._visualFormat = visualFormat; var constraints = AutoLayout.VisualFormat.parse(visualFormat, parseOptions || Autolayout.DEFAULT_PARSE_OPTIONS); this.#metaInfo = AutoLayout.VisualFormat.parseMetaInfo(visualFormat); this.#autoLayoutView = new AutoLayout.View({ constraints: constraints, }); this.#checkNodes(); this.reflowLayout(); return this; } /** * Sets the options such as viewport, spacing, etc... * * @param {Object} options Layout-options to set. * @return {Autolayout} this */ setLayoutOptions(options) { this.#layoutOptions = options || {}; this.reflowLayout(); return this; } /** * Adds a new child to this node. If this method is called with no argument it will * create a new node, however it can also be called with an existing node which it will * append to the node that this method is being called on. Returns the new or passed in node. * * @param {Element3D|void} child The node to appended or no node to create a new node. * @param {String} id Unique id of the node which matches the id used in the Visual format. * @return {Element3D} the appended node. */ addToLayout(child, id) { // PORTED this.append(child); // PORTED // TODO instead of handling nodes here, we should handle them in // childComposedCallback, to support ShadowDOM. if (id) this.#idToNode[id] = child; this.reflowLayout(); return child; } /** * Removes a child node from another node. The passed in node must be * a child of the node that this method is called upon. * * @param {Element3D} [child] node to be removed * @param {String} [id] Unique id of the node which matches the id used in the Visual format. */ removeFromLayout(child, id) { // PORTED if (child && id) { this.removeChild(child); // PORTED delete this.#idToNode[id]; } else if (child) { for (id in this.#idToNode) { if (this.#idToNode[id] === child) { delete this.#idToNode[id]; break; } } this.removeChild(child); // PORTED } else if (id) { this.removeChild(this.#idToNode[id]); // PORTED delete this.#idToNode[id]; } this.reflowLayout(); } #layoutOptions = {}; #idToNode = {}; #reflowLayout = false; #metaInfo; #setIntrinsicWidths(widths) { for (var key in widths) { var subView = this.#autoLayoutView.subViews[key]; var node = this.#idToNode[key]; if (subView && node) { subView.intrinsicWidth = node.calculatedSize.x; // PORTED } } } #setIntrinsicHeights(heights) { for (var key in heights) { var subView = this.#autoLayoutView.subViews[key]; var node = this.#idToNode[key]; if (subView && node) { subView.intrinsicHeight = node.calculatedSize.y; // PORTED } } } #setViewPortSize(size, vp) { size = [ vp.width !== undefined && vp.width !== true ? vp.width : Math.max(Math.min(size[0], vp['max-width'] || size[0]), vp['min-width'] || 0), vp.height !== undefined && vp.height !== true ? vp.height : Math.max(Math.min(size[1] /*TODO no !*/, vp['max-height'] || size[1]), vp['min-height'] || 0), ]; if (vp.width === true && vp.height === true) { size[0] = this.#autoLayoutView.fittingWidth; size[1] = this.#autoLayoutView.fittingHeight; } else if (vp.width === true) { this.#autoLayoutView.setSize(undefined, size[1]); size[0] = this.#autoLayoutView.fittingWidth; // TODO ASPECT RATIO? } else if (vp.height === true) { this.#autoLayoutView.setSize(size[0], undefined); size[1] = this.#autoLayoutView.fittingHeight; // TODO ASPECT RATIO? } else { size = vp['aspect-ratio'] ? [Math.min(size[0], size[1] * vp['aspect-ratio']), Math.min(size[1], size[0] / vp['aspect-ratio'])] : size; this.#autoLayoutView.setSize(size[0], size[1]); } return size; } #layout = () => { if (!this.#autoLayoutView) { return; } var x; var y; var size = this.size.toArray(); if (this.#layoutOptions.spacing || this.#metaInfo.spacing) { this.#autoLayoutView.setSpacing(this.#layoutOptions.spacing || this.#metaInfo.spacing); } var widths = this.#layoutOptions.widths || this.#metaInfo.widths; if (widths) { this.#setIntrinsicWidths(widths); } var heights = this.#layoutOptions.heights || this.#metaInfo.heights; if (heights) { this.#setIntrinsicHeights(heights); } if (this.#layoutOptions.viewport || this.#metaInfo.viewport) { var restrainedSize = this.#setViewPortSize(size, this.#layoutOptions.viewport || this.#metaInfo.viewport); x = (size[0] - restrainedSize[0]) / 2; y = (size[1] - restrainedSize[1]) / 2; } else { this.#autoLayoutView.setSize(size[0], size[1]); x = 0; y = 0; } for (var key in this.#autoLayoutView.subViews) { var subView = this.#autoLayoutView.subViews[key]; if (key.indexOf('_') !== 0 && subView.type !== 'stack') { var node = this.#idToNode[key]; if (node) { this.#updateNode(node, subView, x, y, widths, heights); } } } if (this.#reflowLayout) { this.#reflowLayout = false; } }; #updateNode(node, subView, x, y, widths, heights) { // NOTE The following sizeMode, size, and position functions are no-ops, // they only perform type casting for use in TypeScript code. Without // them there will be type errors. node.sizeMode = [ // PORTED // @ts-ignore: TODO, key is not defined from anywhere, but it was working??? widths && widths[key] === true ? 'proportional' : 'literal', // @ts-ignore: TODO, key is not defined from anywhere, but it was working??? heights && heights[key] === true ? 'proportional' : 'literal', ]; node.size = [ // PORTED // @ts-ignore: TODO, key is not defined from anywhere, but it was working??? widths && widths[key] === true ? 1 : subView.width, // @ts-ignore: TODO, key is not defined from anywhere, but it was working??? heights && heights[key] === true ? 1 : subView.height, ]; node.position = [ // PORTED x + subView.left, y + subView.top, subView.zIndex * 5, ]; } #checkNodes() { const subViews = this.#autoLayoutView.subViews; const subViewKeys = Object.keys(subViews); const _idToNode = this.#idToNode; // If a node is not found for a subview key, see if exists in this's DOM children by className // XXX Should we use a `data-*` attribute instead of a class name? for (var key of subViewKeys) { var subView = subViews[key]; if (key.indexOf('_') !== 0 && subView.type !== 'stack') { var node = _idToNode[key]; if (!node) { node = this.querySelector('.' + key); if (node && node.parentElement === this) _idToNode[key] = node; } } } this.#showOrHideNodes(); } #showOrHideNodes() { const subViews = this.#autoLayoutView.subViews; const subViewKeys = Object.keys(subViews); const _idToNode = this.#idToNode; const nodeIds = Object.keys(_idToNode); // hide the child nodes that are should not be visible for the current subview. for (const id of nodeIds) { if (subViewKeys.includes(id)) _idToNode[id].visible = true; else _idToNode[id].visible = false; } } static { __runInitializers(_classThis, _classExtraInitializers); } }; return Autolayout = _classThis; })(); export { Autolayout }; //# sourceMappingURL=Autolayout.js.map