UNPKG

@nent/core

Version:

Functional elements to add routing, data-binding, dynamic HTML, declarative actions, audio, video, and so much more. Supercharge static HTML files into web apps without script or builds.

496 lines (495 loc) 15 kB
/*! * NENT 2022 */ import { Component, Element, h, Host, Method, Prop, State, } from '@stencil/core'; import { commonState, CommonStateSubscriber, debugIf, slugify, } from '../../services/common'; import { warn } from '../../services/common/logging'; import { replaceHtmlInElement } from '../../services/content/elements'; import { resolveRemoteContentElement } from '../../services/content/remote'; import { DATA_EVENTS } from '../../services/data/interfaces'; import { routingState } from '../n-views/services/state'; import { resolveNext } from './services/next'; import { markVisit } from './services/visits'; /** * The View element holds a segment of content visible only when * a URL path matches. It defines a route and its content. * This provides the declarative mechanism * for in-page content/component routing by URL. * * @slot default - The default slot is rendered when this route is * activated, visible by default to all routes matching * the route URL (typically, child routes). * * @slot content - The content route is rendered only when the route * matches EXACTLY. Note: This HTML is removed when the * route changes. * @system routing * * @extension data * @extension elements */ export class View { constructor() { this.match = null; this.exactMatch = false; this.srcElement = null; this.contentElement = null; /** * The title for this view. This is prefixed * before the app title configured in n-views * */ this.pageTitle = ''; /** * The page description for this view. * */ this.pageDescription = ''; /** * The keywords to add to the keywords meta-tag for this view. * */ this.pageKeywords = ''; /** * The robots instruction for search indexing * */ this.pageRobots = 'all'; /** * Header height or offset for scroll-top on this * view. */ this.scrollTopOffset = 0; /** * The path for this route should only be matched * when it is exact. */ this.exact = false; /** * Cross Origin Mode if the content is pulled from * a remote location */ this.mode = 'cors'; /** * Before rendering remote HTML, replace any data-tokens with their * resolved values. This also commands this element to * re-render it's HTML for data-changes. This can affect * performance. * * IMPORTANT: ONLY WORKS ON REMOTE HTML */ this.resolveTokens = false; /** * Turn on debug statements for load, update and render events. */ this.debug = false; /** * Force render with data & route changes. */ this.noCache = false; } /** * Return all child elements used for processing. This function is * primarily meant for testing. * */ async getChildren() { return { activators: this.route.actionActivators, views: this.childViews, dos: this.childPrompts, }; } get parent() { var _a; return ((_a = this.el.parentElement) === null || _a === void 0 ? void 0 : _a.closest('n-view')) || null; } get childPrompts() { return Array.from(this.el.querySelectorAll('n-view-prompt') || []).filter(e => this.route.isChild(e)); } get childViews() { return Array.from(this.el.querySelectorAll('n-view') || []).filter(e => this.route.isChild(e)); } async componentWillLoad() { debugIf(this.debug, `n-view: ${this.path} loading`); this.contentKey = `rem-content-${slugify(this.contentSrc || 'none')}`; this.srcKey = `rem-source-${slugify(this.src || 'none')}`; if (!routingState.router) { warn(`n-view: ${this.path} cannot load outside of an n-views element`); return; } this.route = routingState.router.createRoute(this.el, this.parent, (match) => { this.match = match ? Object.assign({}, match) : null; this.exactMatch = (match === null || match === void 0 ? void 0 : match.isExact) || false; }); if (commonState.dataEnabled && this.resolveTokens) { this.dataSubscription = new CommonStateSubscriber(this, 'dataEnabled', DATA_EVENTS.DataChanged); } } async componentWillRender() { var _a; debugIf(this.debug, `n-view: ${this.path} will render`); if (this.match) { debugIf(this.debug, `n-view: ${this.path} route is matched `); if (this.src && this.srcElement == null) { this.srcElement = await resolveRemoteContentElement(window, this.src, this.mode, this.srcKey, this.resolveTokens); replaceHtmlInElement(this.el, `#${this.srcKey}`, this.srcElement); } debugIf(this.debug, `n-view: ${this.path} found ${this.childViews.length} child views and` + ` ${this.childPrompts.length} child view-prompts`); // exact-match if (this.match.isExact) { debugIf(this.debug, `n-view: ${this.path} route exactly matched `); const viewDos = this.childPrompts.map(el => { const { path, when, visit } = el; return { path: this.route.normalizeChildUrl(path), when, visit, }; }); const nextDo = await resolveNext(viewDos); if (nextDo) { this.route.replaceWithRoute(nextDo.path); return; } else { if (this.contentSrc && this.contentElement == null) this.contentElement = await resolveRemoteContentElement(window, this.contentSrc, this.mode, this.contentKey, this.resolveTokens, 'content'); markVisit((_a = this.match) === null || _a === void 0 ? void 0 : _a.url); } } } } render() { debugIf(this.debug, `n-view: ${this.path} render`); replaceHtmlInElement(this.el, `#${this.contentKey}`, this.contentElement); return (h(Host, null, h("slot", null), h("slot", { name: "content" }))); } async componentDidRender() { var _a, _b, _c, _d; if (!((_b = (_a = this.route) === null || _a === void 0 ? void 0 : _a.match) === null || _b === void 0 ? void 0 : _b.isExact)) { (_c = this.contentElement) === null || _c === void 0 ? void 0 : _c.remove(); if (this.noCache) this.contentElement = null; } await ((_d = this.route) === null || _d === void 0 ? void 0 : _d.loadCompleted()); } disconnectedCallback() { var _a, _b; (_a = this.dataSubscription) === null || _a === void 0 ? void 0 : _a.destroy(); (_b = this.route) === null || _b === void 0 ? void 0 : _b.destroy(); } static get is() { return "n-view"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["view.css"] }; } static get styleUrls() { return { "$": ["view.css"] }; } static get properties() { return { "route": { "type": "unknown", "mutable": true, "complexType": { "original": "Route", "resolved": "Route", "references": { "Route": { "location": "import", "path": "./services/route" } } }, "required": true, "optional": false, "docs": { "tags": [], "text": "Route information" } }, "pageTitle": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The title for this view. This is prefixed\nbefore the app title configured in n-views" }, "attribute": "page-title", "reflect": false, "defaultValue": "''" }, "pageDescription": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The page description for this view." }, "attribute": "page-description", "reflect": false, "defaultValue": "''" }, "pageKeywords": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The keywords to add to the keywords meta-tag for this view." }, "attribute": "page-keywords", "reflect": false, "defaultValue": "''" }, "pageRobots": { "type": "string", "mutable": false, "complexType": { "original": "'all' | 'noindex' | 'nofollow' | 'none'", "resolved": "\"all\" | \"nofollow\" | \"noindex\" | \"none\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The robots instruction for search indexing" }, "attribute": "page-robots", "reflect": false, "defaultValue": "'all'" }, "scrollTopOffset": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Header height or offset for scroll-top on this\nview." }, "attribute": "scroll-top-offset", "reflect": false, "defaultValue": "0" }, "transition": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Navigation transition between routes.\nThis is a CSS animation class." }, "attribute": "transition", "reflect": false }, "path": { "type": "string", "mutable": true, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": true, "optional": false, "docs": { "tags": [], "text": "The path for this route, including the parent's\nroutes, excluding the router's root." }, "attribute": "path", "reflect": true }, "exact": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The path for this route should only be matched\nwhen it is exact." }, "attribute": "exact", "reflect": false, "defaultValue": "false" }, "src": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Remote URL for this route's HTML. HTML from this\nURL will be not be assigned to any slot.\n\nYou can add slot='content' to any containers\nwithin this HTML if you have a mix of HTML for\nthis exact-route and its children." }, "attribute": "src", "reflect": false }, "contentSrc": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Remote URL for this Route's content." }, "attribute": "content-src", "reflect": false }, "mode": { "type": "string", "mutable": false, "complexType": { "original": "'cors' | 'navigate' | 'no-cors' | 'same-origin'", "resolved": "\"cors\" | \"navigate\" | \"no-cors\" | \"same-origin\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Cross Origin Mode if the content is pulled from\na remote location" }, "attribute": "mode", "reflect": false, "defaultValue": "'cors'" }, "resolveTokens": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Before rendering remote HTML, replace any data-tokens with their\nresolved values. This also commands this element to\nre-render it's HTML for data-changes. This can affect\nperformance.\n\nIMPORTANT: ONLY WORKS ON REMOTE HTML" }, "attribute": "resolve-tokens", "reflect": false, "defaultValue": "false" }, "debug": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Turn on debug statements for load, update and render events." }, "attribute": "debug", "reflect": false, "defaultValue": "false" }, "noCache": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Force render with data & route changes." }, "attribute": "no-cache", "reflect": false, "defaultValue": "false" } }; } static get states() { return { "match": {}, "exactMatch": {} }; } static get methods() { return { "getChildren": { "complexType": { "signature": "() => Promise<{ activators: HTMLNActionActivatorElement[]; views: HTMLNViewElement[]; dos: HTMLNViewPromptElement[]; }>", "parameters": [], "references": { "Promise": { "location": "global" }, "HTMLNActionActivatorElement": { "location": "global" }, "HTMLNViewElement": { "location": "global" }, "HTMLNViewPromptElement": { "location": "global" } }, "return": "Promise<{ activators: HTMLNActionActivatorElement[]; views: HTMLNViewElement[]; dos: HTMLNViewPromptElement[]; }>" }, "docs": { "text": "Return all child elements used for processing. This function is\nprimarily meant for testing.", "tags": [] } } }; } static get elementRef() { return "el"; } }