/** * @typedef {Object} Hyperscript */ (function (self, factory) { const _hyperscript = factory(self) if (typeof exports === 'object' && typeof exports['nodeName'] !== 'string') { module.exports = _hyperscript } else { self['_hyperscript'] = _hyperscript if ('document' in self) self['_hyperscript'].browserInit() } })(typeof self !== 'undefined' ? self : this, (globalScope) => { 'use strict'; /** * @type {Object} * @property {DynamicConverter[]} dynamicResolvers * * @callback DynamicConverter * @param {String} str * @param {*} value * @returns {*} */ const conversions = { dynamicResolvers: [ function(str, value){ if (str === "Fixed") { return Number(value).toFixed(); } else if (str.indexOf("Fixed:") === 0) { let num = str.split(":")[1]; return Number(value).toFixed(parseInt(num)); } } ], String: function (val) { if (val.toString) { return val.toString(); } else { return "" + val; } }, Int: function (val) { return parseInt(val); }, Float: function (val) { return parseFloat(val); }, Number: function (val) { return Number(val); }, Date: function (val) { return new Date(val); }, Array: function (val) { return Array.from(val); }, JSON: function (val) { return JSON.stringify(val); }, Object: function (val) { if (val instanceof String) { val = val.toString(); } if (typeof val === "string") { return JSON.parse(val); } else { return Object.assign({}, val); } }, } const config = { attributes: "_, script, data-script", defaultTransition: "all 500ms ease-in", disableSelector: "[disable-scripting], [data-disable-scripting]", hideShowStrategies: {}, conversions, } class Lexer { static OP_TABLE = { "+": "PLUS", "-": "MINUS", "*": "MULTIPLY", "/": "DIVIDE", ".": "PERIOD", "..": "ELLIPSIS", "\\": "BACKSLASH", ":": "COLON", "%": "PERCENT", "|": "PIPE", "!": "EXCLAMATION", "?": "QUESTION", "#": "POUND", "&": "AMPERSAND", $: "DOLLAR", ";": "SEMI", ",": "COMMA", "(": "L_PAREN", ")": "R_PAREN", "<": "L_ANG", ">": "R_ANG", "<=": "LTE_ANG", ">=": "GTE_ANG", "==": "EQ", "===": "EQQ", "!=": "NEQ", "!==": "NEQQ", "{": "L_BRACE", "}": "R_BRACE", "[": "L_BRACKET", "]": "R_BRACKET", "=": "EQUALS", }; /** * isValidCSSClassChar returns `true` if the provided character is valid in a CSS class. * @param {string} c * @returns boolean */ static isValidCSSClassChar(c) { return Lexer.isAlpha(c) || Lexer.isNumeric(c) || c === "-" || c === "_" || c === ":"; } /** * isValidCSSIDChar returns `true` if the provided character is valid in a CSS ID * @param {string} c * @returns boolean */ static isValidCSSIDChar(c) { return Lexer.isAlpha(c) || Lexer.isNumeric(c) || c === "-" || c === "_" || c === ":"; } /** * isWhitespace returns `true` if the provided character is whitespace. * @param {string} c * @returns boolean */ static isWhitespace(c) { return c === " " || c === "\t" || Lexer.isNewline(c); } /** * positionString returns a string representation of a Token's line and column details. * @param {Token} token * @returns string */ static positionString(token) { return "[Line: " + token.line + ", Column: " + token.column + "]"; } /** * isNewline returns `true` if the provided character is a carrage return or newline * @param {string} c * @returns boolean */ static isNewline(c) { return c === "\r" || c === "\n"; } /** * isNumeric returns `true` if the provided character is a number (0-9) * @param {string} c * @returns boolean */ static isNumeric(c) { return c >= "0" && c <= "9"; } /** * isAlpha returns `true` if the provided character is a letter in the alphabet * @param {string} c * @returns boolean */ static isAlpha(c) { return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z"); } /** * @param {string} c * @param {boolean} [dollarIsOp] * @returns boolean */ static isIdentifierChar(c, dollarIsOp) { return c === "_" || c === "$"; } /** * @param {string} c * @returns boolean */ static isReservedChar(c) { return c === "`" || c === "^"; } /** * @param {Token[]} tokens * @returns {boolean} */ static isValidSingleQuoteStringStart(tokens) { if (tokens.length > 0) { var previousToken = tokens[tokens.length - 1]; if ( previousToken.type === "IDENTIFIER" || previousToken.type === "CLASS_REF" || previousToken.type === "ID_REF" ) { return false; } if (previousToken.op && (previousToken.value === ">" || previousToken.value === ")")) { return false; } } return true; } /** * @param {string} string * @param {boolean} [template] * @returns {Tokens} */ static tokenize(string, template) { var tokens = /** @type {Token[]}*/ []; var source = string; var position = 0; var column = 0; var line = 1; var lastToken = ""; var templateBraceCount = 0; function inTemplate() { return template && templateBraceCount === 0; } while (position < source.length) { if ((currentChar() === "-" && nextChar() === "-" && (Lexer.isWhitespace(nextCharAt(2)) || nextCharAt(2) === "" || nextCharAt(2) === "-")) || (currentChar() === "/" && nextChar() === "/" && (Lexer.isWhitespace(nextCharAt(2)) || nextCharAt(2) === "" || nextCharAt(2) === "/"))) { consumeComment(); } else if (currentChar() === "/" && nextChar() === "*" && (Lexer.isWhitespace(nextCharAt(2)) || nextCharAt(2) === "" || nextCharAt(2) === "*")) { consumeCommentMultiline(); } else { if (Lexer.isWhitespace(currentChar())) { tokens.push(consumeWhitespace()); } else if ( !possiblePrecedingSymbol() && currentChar() === "." && (Lexer.isAlpha(nextChar()) || nextChar() === "{" || nextChar() === "-") ) { tokens.push(consumeClassReference()); } else if ( !possiblePrecedingSymbol() && currentChar() === "#" && (Lexer.isAlpha(nextChar()) || nextChar() === "{") ) { tokens.push(consumeIdReference()); } else if (currentChar() === "[" && nextChar() === "@") { tokens.push(consumeAttributeReference()); } else if (currentChar() === "@") { tokens.push(consumeShortAttributeReference()); } else if (currentChar() === "*" && Lexer.isAlpha(nextChar())) { tokens.push(consumeStyleReference()); } else if (Lexer.isAlpha(currentChar()) || (!inTemplate() && Lexer.isIdentifierChar(currentChar()))) { tokens.push(consumeIdentifier()); } else if (Lexer.isNumeric(currentChar())) { tokens.push(consumeNumber()); } else if (!inTemplate() && (currentChar() === '"' || currentChar() === "`")) { tokens.push(consumeString()); } else if (!inTemplate() && currentChar() === "'") { if (Lexer.isValidSingleQuoteStringStart(tokens)) { tokens.push(consumeString()); } else { tokens.push(consumeOp()); } } else if (Lexer.OP_TABLE[currentChar()]) { if (lastToken === "$" && currentChar() === "{") { templateBraceCount++; } if (currentChar() === "}") { templateBraceCount--; } tokens.push(consumeOp()); } else if (inTemplate() || Lexer.isReservedChar(currentChar())) { tokens.push(makeToken("RESERVED", consumeChar())); } else { if (position < source.length) { throw Error("Unknown token: " + currentChar() + " "); } } } } return new Tokens(tokens, [], source); /** * @param {string} [type] * @param {string} [value] * @returns {Token} */ function makeOpToken(type, value) { var token = makeToken(type, value); token.op = true; return token; } /** * @param {string} [type] * @param {string} [value] * @returns {Token} */ function makeToken(type, value) { return { type: type, value: value || "", start: position, end: position + 1, column: column, line: line, }; } function consumeComment() { while (currentChar() && !Lexer.isNewline(currentChar())) { consumeChar(); } consumeChar(); // Consume newline } function consumeCommentMultiline() { while (currentChar() && !(currentChar() === '*' && nextChar() === '/')) { consumeChar(); } consumeChar(); // Consume "*/" consumeChar(); } /** * @returns Token */ function consumeClassReference() { var classRef = makeToken("CLASS_REF"); var value = consumeChar(); if (currentChar() === "{") { classRef.template = true; value += consumeChar(); while (currentChar() && currentChar() !== "}") { value += consumeChar(); } if (currentChar() !== "}") { throw Error("Unterminated class reference"); } else { value += consumeChar(); // consume final curly } } else { while (Lexer.isValidCSSClassChar(currentChar())) { value += consumeChar(); } } classRef.value = value; classRef.end = position; return classRef; } /** * @returns Token */ function consumeAttributeReference() { var attributeRef = makeToken("ATTRIBUTE_REF"); var value = consumeChar(); while (position < source.length && currentChar() !== "]") { value += consumeChar(); } if (currentChar() === "]") { value += consumeChar(); } attributeRef.value = value; attributeRef.end = position; return attributeRef; } function consumeShortAttributeReference() { var attributeRef = makeToken("ATTRIBUTE_REF"); var value = consumeChar(); while (Lexer.isValidCSSIDChar(currentChar())) { value += consumeChar(); } if (currentChar() === '=') { value += consumeChar(); if (currentChar() === '"' || currentChar() === "'") { let stringValue = consumeString(); value += stringValue.value; } else if(Lexer.isAlpha(currentChar()) || Lexer.isNumeric(currentChar()) || Lexer.isIdentifierChar(currentChar())) { let id = consumeIdentifier(); value += id.value; } } attributeRef.value = value; attributeRef.end = position; return attributeRef; } function consumeStyleReference() { var styleRef = makeToken("STYLE_REF"); var value = consumeChar(); while (Lexer.isAlpha(currentChar()) || currentChar() === "-") { value += consumeChar(); } styleRef.value = value; styleRef.end = position; return styleRef; } /** * @returns Token */ function consumeIdReference() { var idRef = makeToken("ID_REF"); var value = consumeChar(); if (currentChar() === "{") { idRef.template = true; value += consumeChar(); while (currentChar() && currentChar() !== "}") { value += consumeChar(); } if (currentChar() !== "}") { throw Error("Unterminated id reference"); } else { consumeChar(); // consume final quote } } else { while (Lexer.isValidCSSIDChar(currentChar())) { value += consumeChar(); } } idRef.value = value; idRef.end = position; return idRef; } /** * @returns Token */ function consumeIdentifier() { var identifier = makeToken("IDENTIFIER"); var value = consumeChar(); while (Lexer.isAlpha(currentChar()) || Lexer.isNumeric(currentChar()) || Lexer.isIdentifierChar(currentChar())) { value += consumeChar(); } if (currentChar() === "!" && value === "beep") { value += consumeChar(); } identifier.value = value; identifier.end = position; return identifier; } /** * @returns Token */ function consumeNumber() { var number = makeToken("NUMBER"); var value = consumeChar(); // given possible XXX.YYY(e|E)[-]ZZZ consume XXX while (Lexer.isNumeric(currentChar())) { value += consumeChar(); } // consume .YYY if (currentChar() === "." && Lexer.isNumeric(nextChar())) { value += consumeChar(); } while (Lexer.isNumeric(currentChar())) { value += consumeChar(); } // consume (e|E)[-] if (currentChar() === "e" || currentChar() === "E") { // possible scientific notation, e.g. 1e6 or 1e-6 if (Lexer.isNumeric(nextChar())) { // e.g. 1e6 value += consumeChar(); } else if (nextChar() === "-") { // e.g. 1e-6 value += consumeChar(); // consume the - as well since otherwise we would stop on the next loop value += consumeChar(); } } // consume ZZZ while (Lexer.isNumeric(currentChar())) { value += consumeChar(); } number.value = value; number.end = position; return number; } /** * @returns Token */ function consumeOp() { var op = makeOpToken(); var value = consumeChar(); // consume leading char while (currentChar() && Lexer.OP_TABLE[value + currentChar()]) { value += consumeChar(); } op.type = Lexer.OP_TABLE[value]; op.value = value; op.end = position; return op; } /** * @returns Token */ function consumeString() { var string = makeToken("STRING"); var startChar = consumeChar(); // consume leading quote var value = ""; while (currentChar() && currentChar() !== startChar) { if (currentChar() === "\\") { consumeChar(); // consume escape char and get the next one let nextChar = consumeChar(); if (nextChar === "b") { value += "\b"; } else if (nextChar === "f") { value += "\f"; } else if (nextChar === "n") { value += "\n"; } else if (nextChar === "r") { value += "\r"; } else if (nextChar === "t") { value += "\t"; } else if (nextChar === "v") { value += "\v"; } else { value += nextChar; } } else { value += consumeChar(); } } if (currentChar() !== startChar) { throw Error("Unterminated string at " + Lexer.positionString(string)); } else { consumeChar(); // consume final quote } string.value = value; string.end = position; string.template = startChar === "`"; return string; } /** * @returns string */ function currentChar() { return source.charAt(position); } /** * @returns string */ function nextChar() { return source.charAt(position + 1); } function nextCharAt(number = 1) { return source.charAt(position + number); } /** * @returns string */ function consumeChar() { lastToken = currentChar(); position++; column++; return lastToken; } /** * @returns boolean */ function possiblePrecedingSymbol() { return ( Lexer.isAlpha(lastToken) || Lexer.isNumeric(lastToken) || lastToken === ")" || lastToken === "\"" || lastToken === "'" || lastToken === "`" || lastToken === "}" || lastToken === "]" ); } /** * @returns Token */ function consumeWhitespace() { var whitespace = makeToken("WHITESPACE"); var value = ""; while (currentChar() && Lexer.isWhitespace(currentChar())) { if (Lexer.isNewline(currentChar())) { column = 0; line++; } value += consumeChar(); } whitespace.value = value; whitespace.end = position; return whitespace; } } /** * @param {string} string * @param {boolean} [template] * @returns {Tokens} */ tokenize(string, template) { return Lexer.tokenize(string, template) } } /** * @typedef {Object} Token * @property {string} [type] * @property {string} value * @property {number} [start] * @property {number} [end] * @property {number} [column] * @property {number} [line] * @property {boolean} [op] `true` if this token represents an operator * @property {boolean} [template] `true` if this token is a template, for class refs, id refs, strings */ class Tokens { constructor(tokens, consumed, source) { this.tokens = tokens this.consumed = consumed this.source = source this.consumeWhitespace(); // consume initial whitespace } get list() { return this.tokens } /** @type Token | null */ _lastConsumed = null; consumeWhitespace() { while (this.token(0, true).type === "WHITESPACE") { this.consumed.push(this.tokens.shift()); } } /** * @param {Tokens} tokens * @param {*} error * @returns {never} */ raiseError(tokens, error) { Parser.raiseParseError(tokens, error); } /** * @param {string} value * @returns {Token} */ requireOpToken(value) { var token = this.matchOpToken(value); if (token) { return token; } else { this.raiseError(this, "Expected '" + value + "' but found '" + this.currentToken().value + "'"); } } /** * @param {string} op1 * @param {string} [op2] * @param {string} [op3] * @returns {Token | void} */ matchAnyOpToken(op1, op2, op3) { for (var i = 0; i < arguments.length; i++) { var opToken = arguments[i]; var match = this.matchOpToken(opToken); if (match) { return match; } } } /** * @param {string} op1 * @param {string} [op2] * @param {string} [op3] * @returns {Token | void} */ matchAnyToken(op1, op2, op3) { for (var i = 0; i < arguments.length; i++) { var opToken = arguments[i]; var match = this.matchToken(opToken); if (match) { return match; } } } /** * @param {string} value * @returns {Token | void} */ matchOpToken(value) { if (this.currentToken() && this.currentToken().op && this.currentToken().value === value) { return this.consumeToken(); } } /** * @param {string} type1 * @param {string} [type2] * @param {string} [type3] * @param {string} [type4] * @returns {Token} */ requireTokenType(type1, type2, type3, type4) { var token = this.matchTokenType(type1, type2, type3, type4); if (token) { return token; } else { this.raiseError(this, "Expected one of " + JSON.stringify([type1, type2, type3])); } } /** * @param {string} type1 * @param {string} [type2] * @param {string} [type3] * @param {string} [type4] * @returns {Token | void} */ matchTokenType(type1, type2, type3, type4) { if ( this.currentToken() && this.currentToken().type && [type1, type2, type3, type4].indexOf(this.currentToken().type) >= 0 ) { return this.consumeToken(); } } /** * @param {string} value * @param {string} [type] * @returns {Token} */ requireToken(value, type) { var token = this.matchToken(value, type); if (token) { return token; } else { this.raiseError(this, "Expected '" + value + "' but found '" + this.currentToken().value + "'"); } } peekToken(value, peek, type) { peek = peek || 0; type = type || "IDENTIFIER"; return this.tokens[peek] && this.tokens[peek].value === value && this.tokens[peek].type === type } /** * @param {string} value * @param {string} [type] * @returns {Token | void} */ matchToken(value, type) { if (this.follows.indexOf(value) !== -1) { return; // disallowed token here } type = type || "IDENTIFIER"; if (this.currentToken() && this.currentToken().value === value && this.currentToken().type === type) { return this.consumeToken(); } } /** * @returns {Token} */ consumeToken() { var match = this.tokens.shift(); this.consumed.push(match); this._lastConsumed = match; this.consumeWhitespace(); // consume any whitespace return match; } /** * @param {string | null} value * @param {string | null} [type] * @returns {Token[]} */ consumeUntil(value, type) { /** @type Token[] */ var tokenList = []; var currentToken = this.token(0, true); while ( (type == null || currentToken.type !== type) && (value == null || currentToken.value !== value) && currentToken.type !== "EOF" ) { var match = this.tokens.shift(); this.consumed.push(match); tokenList.push(currentToken); currentToken = this.token(0, true); } this.consumeWhitespace(); // consume any whitespace return tokenList; } /** * @returns {string} */ lastWhitespace() { if (this.consumed[this.consumed.length - 1] && this.consumed[this.consumed.length - 1].type === "WHITESPACE") { return this.consumed[this.consumed.length - 1].value; } else { return ""; } } consumeUntilWhitespace() { return this.consumeUntil(null, "WHITESPACE"); } /** * @returns {boolean} */ hasMore() { return this.tokens.length > 0; } /** * @param {number} n * @param {boolean} [dontIgnoreWhitespace] * @returns {Token} */ token(n, dontIgnoreWhitespace) { var /**@type {Token}*/ token; var i = 0; do { if (!dontIgnoreWhitespace) { while (this.tokens[i] && this.tokens[i].type === "WHITESPACE") { i++; } } token = this.tokens[i]; n--; i++; } while (n > -1); if (token) { return token; } else { return { type: "EOF", value: "<<>>", }; } } /** * @returns {Token} */ currentToken() { return this.token(0); } /** * @returns {Token | null} */ lastMatch() { return this._lastConsumed; } /** * @returns {string} */ static sourceFor = function () { return this.programSource.substring(this.startToken.start, this.endToken.end); } /** * @returns {string} */ static lineFor = function () { return this.programSource.split("\n")[this.startToken.line - 1]; } follows = []; pushFollow(str) { this.follows.push(str); } popFollow() { this.follows.pop(); } clearFollows() { var tmp = this.follows; this.follows = []; return tmp; } restoreFollows(f) { this.follows = f; } } /** * @callback ParseRule * @param {Parser} parser * @param {Runtime} runtime * @param {Tokens} tokens * @param {*} [root] * @returns {ASTNode | undefined} * * @typedef {Object} ASTNode * @member {boolean} isFeature * @member {string} type * @member {any[]} args * @member {(this: ASTNode, ctx:Context, root:any, ...args:any) => any} op * @member {(this: ASTNode, context?:Context) => any} evaluate * @member {ASTNode} parent * @member {Set} children * @member {ASTNode} root * @member {String} keyword * @member {Token} endToken * @member {ASTNode} next * @member {(context:Context) => ASTNode} resolveNext * @member {EventSource} eventSource * @member {(this: ASTNode) => void} install * @member {(this: ASTNode, context:Context) => void} execute * @member {(this: ASTNode, target: object, source: object, args?: Object) => void} apply * * */ class Parser { /** * * @param {Runtime} runtime */ constructor(runtime) { this.runtime = runtime this.possessivesDisabled = false /* ============================================================================================ */ /* Core hyperscript Grammar Elements */ /* ============================================================================================ */ this.addGrammarElement("feature", function (parser, runtime, tokens) { if (tokens.matchOpToken("(")) { var featureElement = parser.requireElement("feature", tokens); tokens.requireOpToken(")"); return featureElement; } var featureDefinition = parser.FEATURES[tokens.currentToken().value || ""]; if (featureDefinition) { return featureDefinition(parser, runtime, tokens); } }); this.addGrammarElement("command", function (parser, runtime, tokens) { if (tokens.matchOpToken("(")) { const commandElement = parser.requireElement("command", tokens); tokens.requireOpToken(")"); return commandElement; } var commandDefinition = parser.COMMANDS[tokens.currentToken().value || ""]; let commandElement; if (commandDefinition) { commandElement = commandDefinition(parser, runtime, tokens); } else if (tokens.currentToken().type === "IDENTIFIER") { commandElement = parser.parseElement("pseudoCommand", tokens); } if (commandElement) { return parser.parseElement("indirectStatement", tokens, commandElement); } return commandElement; }); this.addGrammarElement("commandList", function (parser, runtime, tokens) { if (tokens.hasMore()) { var cmd = parser.parseElement("command", tokens); if (cmd) { tokens.matchToken("then"); const next = parser.parseElement("commandList", tokens); if (next) cmd.next = next; return cmd; } } return { type: "emptyCommandListCommand", op: function(context){ return runtime.findNext(this, context); }, execute: function (context) { return runtime.unifiedExec(this, context); } } }); this.addGrammarElement("leaf", function (parser, runtime, tokens) { var result = parser.parseAnyOf(parser.LEAF_EXPRESSIONS, tokens); // symbol is last so it doesn't consume any constants if (result == null) { return parser.parseElement("symbol", tokens); } return result; }); this.addGrammarElement("indirectExpression", function (parser, runtime, tokens, root) { for (var i = 0; i < parser.INDIRECT_EXPRESSIONS.length; i++) { var indirect = parser.INDIRECT_EXPRESSIONS[i]; root.endToken = tokens.lastMatch(); var result = parser.parseElement(indirect, tokens, root); if (result) { return result; } } return root; }); this.addGrammarElement("indirectStatement", function (parser, runtime, tokens, root) { if (tokens.matchToken("unless")) { root.endToken = tokens.lastMatch(); var conditional = parser.requireElement("expression", tokens); var unless = { type: "unlessStatementModifier", args: [conditional], op: function (context, conditional) { if (conditional) { return this.next; } else { return root; } }, execute: function (context) { return runtime.unifiedExec(this, context); }, }; root.parent = unless; return unless; } return root; }); this.addGrammarElement("primaryExpression", function (parser, runtime, tokens) { var leaf = parser.parseElement("leaf", tokens); if (leaf) { return parser.parseElement("indirectExpression", tokens, leaf); } parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value); }); } use(plugin) { plugin(this) return this } /** @type {Object} */ GRAMMAR = {}; /** @type {Object} */ COMMANDS = {}; /** @type {Object} */ FEATURES = {}; /** @type {string[]} */ LEAF_EXPRESSIONS = []; /** @type {string[]} */ INDIRECT_EXPRESSIONS = []; /** * @param {*} parseElement * @param {*} start * @param {Tokens} tokens */ initElt(parseElement, start, tokens) { parseElement.startToken = start; parseElement.sourceFor = Tokens.sourceFor; parseElement.lineFor = Tokens.lineFor; parseElement.programSource = tokens.source; } /** * @param {string} type * @param {Tokens} tokens * @param {ASTNode?} root * @returns {ASTNode} */ parseElement(type, tokens, root = undefined) { var elementDefinition = this.GRAMMAR[type]; if (elementDefinition) { var start = tokens.currentToken(); var parseElement = elementDefinition(this, this.runtime, tokens, root); if (parseElement) { this.initElt(parseElement, start, tokens); parseElement.endToken = parseElement.endToken || tokens.lastMatch(); var root = parseElement.root; while (root != null) { this.initElt(root, start, tokens); root = root.root; } } return parseElement; } } /** * @param {string} type * @param {Tokens} tokens * @param {string} [message] * @param {*} [root] * @returns {ASTNode} */ requireElement(type, tokens, message, root) { var result = this.parseElement(type, tokens, root); if (!result) Parser.raiseParseError(tokens, message || "Expected " + type); // @ts-ignore return result; } /** * @param {string[]} types * @param {Tokens} tokens * @returns {ASTNode} */ parseAnyOf(types, tokens) { for (var i = 0; i < types.length; i++) { var type = types[i]; var expression = this.parseElement(type, tokens); if (expression) { return expression; } } } /** * @param {string} name * @param {ParseRule} definition */ addGrammarElement(name, definition) { this.GRAMMAR[name] = definition; } /** * @param {string} keyword * @param {ParseRule} definition */ addCommand(keyword, definition) { var commandGrammarType = keyword + "Command"; var commandDefinitionWrapper = function (parser, runtime, tokens) { const commandElement = definition(parser, runtime, tokens); if (commandElement) { commandElement.type = commandGrammarType; commandElement.execute = function (context) { context.meta.command = commandElement; return runtime.unifiedExec(this, context); }; return commandElement; } }; this.GRAMMAR[commandGrammarType] = commandDefinitionWrapper; this.COMMANDS[keyword] = commandDefinitionWrapper; } /** * @param {string} keyword * @param {ParseRule} definition */ addFeature(keyword, definition) { var featureGrammarType = keyword + "Feature"; /** @type {ParseRule} */ var featureDefinitionWrapper = function (parser, runtime, tokens) { var featureElement = definition(parser, runtime, tokens); if (featureElement) { featureElement.isFeature = true; featureElement.keyword = keyword; featureElement.type = featureGrammarType; return featureElement; } }; this.GRAMMAR[featureGrammarType] = featureDefinitionWrapper; this.FEATURES[keyword] = featureDefinitionWrapper; } /** * @param {string} name * @param {ParseRule} definition */ addLeafExpression(name, definition) { this.LEAF_EXPRESSIONS.push(name); this.addGrammarElement(name, definition); } /** * @param {string} name * @param {ParseRule} definition */ addIndirectExpression(name, definition) { this.INDIRECT_EXPRESSIONS.push(name); this.addGrammarElement(name, definition); } /** * * @param {Tokens} tokens * @returns string */ static createParserContext(tokens) { var currentToken = tokens.currentToken(); var source = tokens.source; var lines = source.split("\n"); var line = currentToken && currentToken.line ? currentToken.line - 1 : lines.length - 1; var contextLine = lines[line]; var offset = /** @type {number} */ ( currentToken && currentToken.line ? currentToken.column : contextLine.length - 1); return contextLine + "\n" + " ".repeat(offset) + "^^\n\n"; } /** * @param {Tokens} tokens * @param {string} [message] * @returns {never} */ static raiseParseError(tokens, message) { message = (message || "Unexpected Token : " + tokens.currentToken().value) + "\n\n" + Parser.createParserContext(tokens); var error = new Error(message); error["tokens"] = tokens; throw error; } /** * @param {Tokens} tokens * @param {string} [message] */ raiseParseError(tokens, message) { Parser.raiseParseError(tokens, message) } /** * @param {Tokens} tokens * @returns {ASTNode} */ parseHyperScript(tokens) { var result = this.parseElement("hyperscript", tokens); if (tokens.hasMore()) this.raiseParseError(tokens); if (result) return result; } /** * @param {ASTNode | undefined} elt * @param {ASTNode} parent */ setParent(elt, parent) { if (typeof elt === 'object') { elt.parent = parent; if (typeof parent === 'object') { parent.children = (parent.children || new Set()); parent.children.add(elt) } this.setParent(elt.next, parent); } } /** * @param {Token} token * @returns {ParseRule} */ commandStart(token) { return this.COMMANDS[token.value || ""]; } /** * @param {Token} token * @returns {ParseRule} */ featureStart(token) { return this.FEATURES[token.value || ""]; } /** * @param {Token} token * @returns {boolean} */ commandBoundary(token) { if ( token.value == "end" || token.value == "then" || token.value == "else" || token.value == "otherwise" || token.value == ")" || this.commandStart(token) || this.featureStart(token) || token.type == "EOF" ) { return true; } return false; } /** * @param {Tokens} tokens * @returns {(string | ASTNode)[]} */ parseStringTemplate(tokens) { /** @type {(string | ASTNode)[]} */ var returnArr = [""]; do { returnArr.push(tokens.lastWhitespace()); if (tokens.currentToken().value === "$") { tokens.consumeToken(); var startingBrace = tokens.matchOpToken("{"); returnArr.push(this.requireElement("expression", tokens)); if (startingBrace) { tokens.requireOpToken("}"); } returnArr.push(""); } else if (tokens.currentToken().value === "\\") { tokens.consumeToken(); // skip next tokens.consumeToken(); } else { var token = tokens.consumeToken(); returnArr[returnArr.length - 1] += token ? token.value : ""; } } while (tokens.hasMore()); returnArr.push(tokens.lastWhitespace()); return returnArr; } /** * @param {ASTNode} commandList */ ensureTerminated(commandList) { const runtime = this.runtime var implicitReturn = { type: "implicitReturn", op: function (context) { context.meta.returned = true; if (context.meta.resolve) { context.meta.resolve(); } return runtime.HALT; }, execute: function (ctx) { // do nothing }, }; var end = commandList; while (end.next) { end = end.next; } end.next = implicitReturn; } } class Runtime { /** * * @param {Lexer} [lexer] * @param {Parser} [parser] */ constructor(lexer, parser) { this.lexer = lexer ?? new Lexer; this.parser = parser ?? new Parser(this) .use(hyperscriptCoreGrammar) .use(hyperscriptWebGrammar); this.parser.runtime = this } /** * @param {HTMLElement} elt * @param {string} selector * @returns boolean */ matchesSelector(elt, selector) { // noinspection JSUnresolvedVariable var matchesFunction = // @ts-ignore elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector; return matchesFunction && matchesFunction.call(elt, selector); } /** * @param {string} eventName * @param {Object} [detail] * @returns {Event} */ makeEvent(eventName, detail) { var evt; if (globalScope.Event && typeof globalScope.Event === "function") { evt = new Event(eventName, { bubbles: true, cancelable: true, }); evt['detail'] = detail; } else { evt = document.createEvent("CustomEvent"); evt.initCustomEvent(eventName, true, true, detail); } return evt; } /** * @param {Element} elt * @param {string} eventName * @param {Object} [detail] * @param {Element} [sender] * @returns {boolean} */ triggerEvent(elt, eventName, detail, sender) { detail = detail || {}; detail["sender"] = sender; var event = this.makeEvent(eventName, detail); var eventResult = elt.dispatchEvent(event); return eventResult; } /** * isArrayLike returns `true` if the provided value is an array or * a NodeList (which is close enough to being an array for our purposes). * * @param {any} value * @returns {value is Array | NodeList} */ isArrayLike(value) { return Array.isArray(value) || (typeof NodeList !== 'undefined' && (value instanceof NodeList || value instanceof HTMLCollection)); } /** * isIterable returns `true` if the provided value supports the * iterator protocol. * * @param {any} value * @returns {value is Iterable} */ isIterable(value) { return typeof value === 'object' && Symbol.iterator in value && typeof value[Symbol.iterator] === 'function'; } /** * shouldAutoIterate returns `true` if the provided value * should be implicitly iterated over when accessing properties, * and as the target of some commands. * * Currently, this is when the value is an {ElementCollection} * or {isArrayLike} returns true. * * @param {any} value * @returns {value is (any[] | ElementCollection)} */ shouldAutoIterate(value) { return value != null && value[shouldAutoIterateSymbol] || this.isArrayLike(value); } /** * forEach executes the provided `func` on every item in the `value` array. * if `value` is a single item (and not an array) then `func` is simply called * once. If `value` is null, then no further actions are taken. * * @template T * @param {T | Iterable} value * @param {(item: T) => void} func */ forEach(value, func) { if (value == null) { // do nothing } else if (this.isIterable(value)) { for (const nth of value) { func(nth); } } else if (this.isArrayLike(value)) { for (var i = 0; i < value.length; i++) { func(value[i]); } } else { func(value); } } /** * implicitLoop executes the provided `func` on: * - every item of {value}, if {value} should be auto-iterated * (see {shouldAutoIterate}) * - {value} otherwise * * @template T * @param {ElementCollection | T | T[]} value * @param {(item: T) => void} func */ implicitLoop(value, func) { if (this.shouldAutoIterate(value)) { for (const x of value) func(x); } else { func(value); } } wrapArrays(args) { var arr = []; for (var i = 0; i < args.length; i++) { var arg = args[i]; if (Array.isArray(arg)) { arr.push(Promise.all(arg)); } else { arr.push(arg); } } return arr; } unwrapAsyncs(values) { for (var i = 0; i < values.length; i++) { var value = values[i]; if (value.asyncWrapper) { values[i] = value.value; } if (Array.isArray(value)) { for (var j = 0; j < value.length; j++) { var valueElement = value[j]; if (valueElement.asyncWrapper) { value[j] = valueElement.value; } } } } } static HALT = {}; HALT = Runtime.HALT; /** * @param {ASTNode} command * @param {Context} ctx */ unifiedExec(command, ctx) { while (true) { try { var next = this.unifiedEval(command, ctx); } catch (e) { if (ctx.meta.handlingFinally) { console.error(" Exception in finally block: ", e); next = Runtime.HALT; } else { this.registerHyperTrace(ctx, e); if (ctx.meta.errorHandler && !ctx.meta.handlingError) { ctx.meta.handlingError = true; ctx.locals[ctx.meta.errorSymbol] = e; command = ctx.meta.errorHandler; continue; } else { ctx.meta.currentException = e; next = Runtime.HALT; } } } if (next == null) { console.error(command, " did not return a next element to execute! context: ", ctx); return; } else if (next.then) { next.then(resolvedNext => { this.unifiedExec(resolvedNext, ctx); }).catch(reason => { this.unifiedExec({ // Anonymous command to simply throw the exception op: function(){ throw reason; } }, ctx); }); return; } else if (next === Runtime.HALT) { if (ctx.meta.finallyHandler && !ctx.meta.handlingFinally) { ctx.meta.handlingFinally = true; command = ctx.meta.finallyHandler; } else { if (ctx.meta.onHalt) { ctx.meta.onHalt(); } if (ctx.meta.currentException) { if (ctx.meta.reject) { ctx.meta.reject(ctx.meta.currentException); return; } else { throw ctx.meta.currentException; } } else { return; } } } else { command = next; // move to the next command } } } /** * @param {*} parseElement * @param {Context} ctx * @returns {*} */ unifiedEval(parseElement, ctx) { /** @type any[] */ var args = [ctx]; var async = false; var wrappedAsyncs = false; if (parseElement.args) { for (var i = 0; i < parseElement.args.length; i++) { var argument = parseElement.args[i]; if (argument == null) { args.push(null); } else if (Array.isArray(argument)) { var arr = []; for (var j = 0; j < argument.length; j++) { var element = argument[j]; var value = element ? element.evaluate(ctx) : null; // OK if (value) { if (value.then) { async = true; } else if (value.asyncWrapper) { wrappedAsyncs = true; } } arr.push(value); } args.push(arr); } else if (argument.evaluate) { var value = argument.evaluate(ctx); // OK if (value) { if (value.then) { async = true; } else if (value.asyncWrapper) { wrappedAsyncs = true; } } args.push(value); } else { args.push(argument); } } } if (async) { return new Promise((resolve, reject) => { args = this.wrapArrays(args); Promise.all(args) .then(function (values) { if (wrappedAsyncs) { this.unwrapAsyncs(values); } try { var apply = parseElement.op.apply(parseElement, values); resolve(apply); } catch (e) { reject(e); } }) .catch(function (reason) { reject(reason); }); }); } else { if (wrappedAsyncs) { this.unwrapAsyncs(args); } return parseElement.op.apply(parseElement, args); } } /** * @type {string[] | null} */ _scriptAttrs = null; /** * getAttributes returns the attribute name(s) to use when * locating hyperscript scripts in a DOM element. If no value * has been configured, it defaults to config.attributes * @returns string[] */ getScriptAttributes() { if (this._scriptAttrs == null) { this._scriptAttrs = config.attributes.replace(/ /g, "").split(","); } return this._scriptAttrs; } /** * @param {Element} elt * @returns {string | null} */ getScript(elt) { for (var i = 0; i < this.getScriptAttributes().length; i++) { var scriptAttribute = this.getScriptAttributes()[i]; if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) { return elt.getAttribute(scriptAttribute); } } if (elt instanceof HTMLScriptElement && elt.type === "text/hyperscript") { return elt.innerText; } return null; } hyperscriptFeaturesMap = new WeakMap /** * @param {*} elt * @returns {Object} */ getHyperscriptFeatures(elt) { var hyperscriptFeatures = this.hyperscriptFeaturesMap.get(elt); if (typeof hyperscriptFeatures === 'undefined') { if (elt) { // in some rare cases, elt is null and this line crashes this.hyperscriptFeaturesMap.set(elt, hyperscriptFeatures = {}); } } return hyperscriptFeatures; } /** * @param {Object} owner * @param {Context} ctx */ addFeatures(owner, ctx) { if (owner) { Object.assign(ctx.locals, this.getHyperscriptFeatures(owner)); this.addFeatures(owner.parentElement, ctx); } } /** * @param {*} owner * @param {*} feature * @param {*} hyperscriptTarget * @param {*} event * @returns {Context} */ makeContext(owner, feature, hyperscriptTarget, event) { return new Context(owner, feature, hyperscriptTarget, event, this) } /** * @returns string */ getScriptSelector() { return this.getScriptAttributes() .map(function (attribute) { return "[" + attribute + "]"; }) .join(", "); } /** * @param {any} value * @param {string} type * @returns {any} */ convertValue(value, type) { var dynamicResolvers = conversions.dynamicResolvers; for (var i = 0; i < dynamicResolvers.length; i++) { var dynamicResolver = dynamicResolvers[i]; var converted = dynamicResolver(type, value); if (converted !== undefined) { return converted; } } if (value == null) { return null; } var converter = conversions[type]; if (converter) { return converter(value); } throw "Unknown conversion : " + type; } /** * @param {string} src * @returns {ASTNode} */ parse(src) { const lexer = this.lexer, parser = this.parser var tokens = lexer.tokenize(src); if (this.parser.commandStart(tokens.currentToken())) { var commandList = parser.requireElement("commandList", tokens); if (tokens.hasMore()) parser.raiseParseError(tokens); parser.ensureTerminated(commandList); return commandList; } else if (parser.featureStart(tokens.currentToken())) { var hyperscript = parser.requireElement("hyperscript", tokens); if (tokens.hasMore()) parser.raiseParseError(tokens); return hyperscript; } else { var expression = parser.requireElement("expression", tokens); if (tokens.hasMore()) parser.raiseParseError(tokens); return expression; } } /** * * @param {ASTNode} elt * @param {Context} ctx * @returns {any} */ evaluateNoPromise(elt, ctx) { let result = elt.evaluate(ctx); if (result.next) { throw new Error(Tokens.sourceFor.call(elt) + " returned a Promise in a context that they are not allowed."); } return result; } /** * @param {string} src * @param {Partial} [ctx] * @param {Object} [args] * @returns {any} */ evaluate(src, ctx, args) { class HyperscriptModule extends EventTarget { constructor(mod) { super(); this.module = mod; } toString() { return this.module.id; } } var body = 'document' in globalScope ? globalScope.document.body : new HyperscriptModule(args && args.module); ctx = Object.assign(this.makeContext(body, null, body, null), ctx || {}); var element = this.parse(src); if (element.execute) { element.execute(ctx); return ctx.result; } else if (element.apply) { element.apply(body, body, args); return this.getHyperscriptFeatures(body); } else { return element.evaluate(ctx); } function makeModule() { return {} } } /** * @param {HTMLElement} elt */ processNode(elt) { var selector = this.getScriptSelector(); if (this.matchesSelector(elt, selector)) { this.initElement(elt, elt); } if (elt instanceof HTMLScriptElement && elt.type === "text/hyperscript") { this.initElement(elt, document.body); } if (elt.querySelectorAll) { this.forEach(elt.querySelectorAll(selector + ", [type='text/hyperscript']"), elt => { this.initElement(elt, elt instanceof HTMLScriptElement && elt.type === "text/hyperscript" ? document.body : elt); }); } } /** * @param {Element} elt * @param {Element} [target] */ initElement(elt, target) { if (elt.closest && elt.closest(config.disableSelector)) { return; } var internalData = this.getInternalData(elt); if (!internalData.initialized) { var src = this.getScript(elt); if (src) { try { internalData.initialized = true; internalData.script = src; const lexer = this.lexer, parser = this.parser var tokens = lexer.tokenize(src); var hyperScript = parser.parseHyperScript(tokens); if (!hyperScript) return; hyperScript.apply(target || elt, elt); setTimeout(() => { this.triggerEvent(target || elt, "load", { hyperscript: true, }); }, 1); } catch (e) { this.triggerEvent(elt, "exception", { error: e, }); console.error( "hyperscript errors were found on the following element:", elt, "\n\n", e.message, e.stack ); } } } } internalDataMap = new WeakMap /** * @param {Element} elt * @returns {Object} */ getInternalData(elt) { var internalData = this.internalDataMap.get(elt); if (typeof internalData === 'undefined') { this.internalDataMap.set(elt, internalData = {}); } return internalData; } /** * @param {any} value * @param {string} typeString * @param {boolean} [nullOk] * @returns {boolean} */ typeCheck(value, typeString, nullOk) { if (value == null && nullOk) { return true; } var typeName = Object.prototype.toString.call(value).slice(8, -1); return typeName === typeString; } getElementScope(context) { var elt = context.meta && context.meta.owner; if (elt) { var internalData = this.getInternalData(elt); var scopeName = "elementScope"; if (context.meta.feature && context.meta.feature.behavior) { scopeName = context.meta.feature.behavior + "Scope"; } var elementScope = getOrInitObject(internalData, scopeName); return elementScope; } else { return {}; // no element, return empty scope } } /** * @param {string} str * @returns {boolean} */ isReservedWord(str) { return ["meta", "it", "result", "locals", "event", "target", "detail", "sender", "body"].includes(str) } /** * @param {any} context * @returns {boolean} */ isHyperscriptContext(context) { return context instanceof Context; } /** * @param {string} str * @param {Context} context * @returns {any} */ resolveSymbol(str, context, type) { if (str === "me" || str === "my" || str === "I") { return context.me; } if (str === "it" || str === "its" || str === "result") { return context.result; } if (str === "you" || str === "your" || str === "yourself") { return context.you; } else { if (type === "global") { return globalScope[str]; } else if (type === "element") { var elementScope = this.getElementScope(context); return elementScope[str]; } else if (type === "local") { return context.locals[str]; } else { // meta scope (used for event conditionals) if (context.meta && context.meta.context) { var fromMetaContext = context.meta.context[str]; if (typeof fromMetaContext !== "undefined") { return fromMetaContext; } // resolve against the `detail` object in the meta context as well if (context.meta.context.detail) { fromMetaContext = context.meta.context.detail[str]; if (typeof fromMetaContext !== "undefined") { return fromMetaContext; } } } if (this.isHyperscriptContext(context) && !this.isReservedWord(str)) { // local scope var fromContext = context.locals[str]; } else { // direct get from normal JS object or top-level of context var fromContext = context[str]; } if (typeof fromContext !== "undefined") { return fromContext; } else { // element scope var elementScope = this.getElementScope(context); fromContext = elementScope[str]; if (typeof fromContext !== "undefined") { return fromContext; } else { // global scope return globalScope[str]; } } } } } setSymbol(str, context, type, value) { if (type === "global") { globalScope[str] = value; } else if (type === "element") { var elementScope = this.getElementScope(context); elementScope[str] = value; } else if (type === "local") { context.locals[str] = value; } else { if (this.isHyperscriptContext(context) && !this.isReservedWord(str) && typeof context.locals[str] !== "undefined") { // local scope context.locals[str] = value; } else { // element scope var elementScope = this.getElementScope(context); var fromContext = elementScope[str]; if (typeof fromContext !== "undefined") { elementScope[str] = value; } else { if (this.isHyperscriptContext(context) && !this.isReservedWord(str)) { // local scope context.locals[str] = value; } else { // direct set on normal JS object or top-level of context context[str] = value; } } } } } /** * @param {ASTNode} command * @param {Context} context * @returns {undefined | ASTNode} */ findNext(command, context) { if (command) { if (command.resolveNext) { return command.resolveNext(context); } else if (command.next) { return command.next; } else { return this.findNext(command.parent, context); } } } /** * @param {Object} root * @param {string} property * @param {Getter} getter * @returns {any} * * @callback Getter * @param {Object} root * @param {string} property */ flatGet(root, property, getter) { if (root != null) { var val = getter(root, property); if (typeof val !== "undefined") { return val; } if (this.shouldAutoIterate(root)) { // flat map var result = []; for (var component of root) { var componentValue = getter(component, property); result.push(componentValue); } return result; } } } resolveProperty(root, property) { return this.flatGet(root, property, (root, property) => root[property] ) } resolveAttribute(root, property) { return this.flatGet(root, property, (root, property) => root.getAttribute && root.getAttribute(property) ) } /** * * @param {Object} root * @param {string} property * @returns {string} */ resolveStyle(root, property) { return this.flatGet(root, property, (root, property) => root.style && root.style[property] ) } /** * * @param {Object} root * @param {string} property * @returns {string} */ resolveComputedStyle(root, property) { return this.flatGet(root, property, (root, property) => getComputedStyle( /** @type {Element} */ (root)).getPropertyValue(property) ) } /** * @param {Element} elt * @param {string[]} nameSpace * @param {string} name * @param {any} value */ assignToNamespace(elt, nameSpace, name, value) { let root if (typeof document !== "undefined" && elt === document.body) { root = globalScope; } else { root = this.getHyperscriptFeatures(elt); } var propertyName; while ((propertyName = nameSpace.shift()) !== undefined) { var newRoot = root[propertyName]; if (newRoot == null) { newRoot = {}; root[propertyName] = newRoot; } root = newRoot; } root[name] = value; } getHyperTrace(ctx, thrown) { var trace = []; var root = ctx; while (root.meta.caller) { root = root.meta.caller; } if (root.meta.traceMap) { return root.meta.traceMap.get(thrown, trace); } } registerHyperTrace(ctx, thrown) { var trace = []; var root = null; while (ctx != null) { trace.push(ctx); root = ctx; ctx = ctx.meta.caller; } if (root.meta.traceMap == null) { root.meta.traceMap = new Map(); // TODO - WeakMap? } if (!root.meta.traceMap.get(thrown)) { var traceEntry = { trace: trace, print: function (logger) { logger = logger || console.error; logger("hypertrace /// "); var maxLen = 0; for (var i = 0; i < trace.length; i++) { maxLen = Math.max(maxLen, trace[i].meta.feature.displayName.length); } for (var i = 0; i < trace.length; i++) { var traceElt = trace[i]; logger( " ->", traceElt.meta.feature.displayName.padEnd(maxLen + 2), "-", traceElt.meta.owner ); } }, }; root.meta.traceMap.set(thrown, traceEntry); } } /** * @param {string} str * @returns {string} */ escapeSelector(str) { return str.replace(/:/g, function (str) { return "\\" + str; }); } /** * @param {any} value * @param {*} elt */ nullCheck(value, elt) { if (value == null) { throw new Error("'" + elt.sourceFor() + "' is null"); } } /** * @param {any} value * @returns {boolean} */ isEmpty(value) { return value == undefined || value.length === 0; } /** * @param {any} value * @returns {boolean} */ doesExist(value) { if(value == null){ return false; } if (this.shouldAutoIterate(value)) { for (const elt of value) { return true; } return false; } return true; } /** * @param {Node} node * @returns {Document|ShadowRoot} */ getRootNode(node) { if (node && node instanceof Node) { var rv = node.getRootNode(); if (rv instanceof Document || rv instanceof ShadowRoot) return rv; } return document; } /** * * @param {Element} elt * @param {ASTNode} onFeature * @returns {EventQueue} * * @typedef {{queue:Array, executing:boolean}} EventQueue */ getEventQueueFor(elt, onFeature) { let internalData = this.getInternalData(elt); var eventQueuesForElt = internalData.eventQueues; if (eventQueuesForElt == null) { eventQueuesForElt = new Map(); internalData.eventQueues = eventQueuesForElt; } var eventQueueForFeature = eventQueuesForElt.get(onFeature); if (eventQueueForFeature == null) { eventQueueForFeature = {queue:[], executing:false}; eventQueuesForElt.set(onFeature, eventQueueForFeature); } return eventQueueForFeature; } beepValueToConsole(element, expression, value) { if (this.triggerEvent(element, "hyperscript:beep", {element, expression, value})) { var typeName; if (value) { if (value instanceof ElementCollection) { typeName = "ElementCollection"; } else if (value.constructor) { typeName = value.constructor.name; } else { typeName = "unknown"; } } else { typeName = "object (null)" } var logValue = value; if (typeName === "String") { logValue = '"' + logValue + '"'; } else if (value instanceof ElementCollection) { logValue = Array.from(value); } console.log("///_ BEEP! The expression (" + Tokens.sourceFor.call(expression).replace("beep! ", "") + ") evaluates to:", logValue, "of type " + typeName); } } /** @type string | null */ // @ts-ignore hyperscriptUrl = "document" in globalScope && document.currentScript ? document.currentScript.src : null; } function getCookiesAsArray() { let cookiesAsArray = document.cookie .split("; ") .map(cookieEntry => { let strings = cookieEntry.split("="); return {name: strings[0], value: decodeURIComponent(strings[1])} }); return cookiesAsArray; } function clearCookie(name) { document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; } function clearAllCookies() { for (const cookie of getCookiesAsArray()) { clearCookie(cookie.name); } } const CookieJar = new Proxy({}, { get(target, prop) { if (prop === 'then' || prop === 'asyncWrapper') { // ignore special symbols return null; } else if (prop === 'length') { return getCookiesAsArray().length } else if (prop === 'clear') { return clearCookie; } else if (prop === 'clearAll') { return clearAllCookies; } else if (typeof prop === "string") { if (!isNaN(prop)) { return getCookiesAsArray()[parseInt(prop)]; } else { let value = document.cookie .split("; ") .find((row) => row.startsWith(prop + "=")) ?.split("=")[1]; if(value) { return decodeURIComponent(value); } } } else if (prop === Symbol.iterator) { return getCookiesAsArray()[prop]; } }, set(target, prop, value) { var finalValue = null; if ('string' === typeof value) { finalValue = encodeURIComponent(value) finalValue += ";samesite=lax" } else { finalValue = encodeURIComponent(value.value); if (value.expires) { finalValue+=";expires=" + value.maxAge; } if (value.maxAge) { finalValue+=";max-age=" + value.maxAge; } if (value.partitioned) { finalValue+=";partitioned=" + value.partitioned; } if (value.path) { finalValue+=";path=" + value.path; } if (value.samesite) { finalValue+=";samesite=" + value.path; } if (value.secure) { finalValue+=";secure=" + value.path; } } document.cookie= prop + "=" + value; return true; } }) class Context { /** * @param {*} owner * @param {*} feature * @param {*} hyperscriptTarget * @param {*} event */ constructor(owner, feature, hyperscriptTarget, event, runtime) { this.meta = { parser: runtime.parser, lexer: runtime.lexer, runtime, owner: owner, feature: feature, iterators: {}, ctx: this } this.locals = { cookies:CookieJar }; this.me = hyperscriptTarget, this.you = undefined this.result = undefined this.event = event; this.target = event ? event.target : null; this.detail = event ? event.detail : null; this.sender = event ? event.detail ? event.detail.sender : null : null; this.body = "document" in globalScope ? document.body : null; runtime.addFeatures(owner, this); } } class ElementCollection { constructor(css, relativeToElement, escape) { this._css = css; this.relativeToElement = relativeToElement; this.escape = escape; this[shouldAutoIterateSymbol] = true; } get css() { if (this.escape) { return Runtime.prototype.escapeSelector(this._css); } else { return this._css; } } get className() { return this._css.substr(1); } get id() { return this.className(); } contains(elt) { for (let element of this) { if (element.contains(elt)) { return true; } } return false; } get length() { return this.selectMatches().length; } [Symbol.iterator]() { let query = this.selectMatches(); return query [Symbol.iterator](); } selectMatches() { let query = Runtime.prototype.getRootNode(this.relativeToElement).querySelectorAll(this.css); return query; } } const shouldAutoIterateSymbol = Symbol() function getOrInitObject(root, prop) { var value = root[prop]; if (value) { return value; } else { var newObj = {}; root[prop] = newObj; return newObj; } } /** * parseJSON parses a JSON string into a corresponding value. If the * value passed in is not valid JSON, then it logs an error and returns `null`. * * @param {string} jString * @returns any */ function parseJSON(jString) { try { return JSON.parse(jString); } catch (error) { logError(error); return null; } } /** * logError writes an error message to the Javascript console. It can take any * value, but msg should commonly be a simple string. * @param {*} msg */ function logError(msg) { if (console.error) { console.error(msg); } else if (console.log) { console.log("ERROR: ", msg); } } // TODO: JSDoc description of what's happening here function varargConstructor(Cls, args) { return new (Cls.bind.apply(Cls, [Cls].concat(args)))(); } // Grammar /** * @param {Parser} parser */ function hyperscriptCoreGrammar(parser) { parser.addLeafExpression("parenthesized", function (parser, _runtime, tokens) { if (tokens.matchOpToken("(")) { var follows = tokens.clearFollows(); try { var expr = parser.requireElement("expression", tokens); } finally { tokens.restoreFollows(follows); } tokens.requireOpToken(")"); return expr; } }); parser.addLeafExpression("string", function (parser, runtime, tokens) { var stringToken = tokens.matchTokenType("STRING"); if (!stringToken) return; var rawValue = /** @type {string} */ (stringToken.value); /** @type {any[]} */ var args; if (stringToken.template) { var innerTokens = Lexer.tokenize(rawValue, true); args = parser.parseStringTemplate(innerTokens); } else { args = []; } return { type: "string", token: stringToken, args: args, op: function (context) { var returnStr = ""; for (var i = 1; i < arguments.length; i++) { var val = arguments[i]; if (val !== undefined) { returnStr += val; } } return returnStr; }, evaluate: function (context) { if (args.length === 0) { return rawValue; } else { return runtime.unifiedEval(this, context); } }, }; }); parser.addGrammarElement("nakedString", function (parser, runtime, tokens) { if (tokens.hasMore()) { var tokenArr = tokens.consumeUntilWhitespace(); tokens.matchTokenType("WHITESPACE"); return { type: "nakedString", tokens: tokenArr, evaluate: function (context) { return tokenArr .map(function (t) { return t.value; }) .join(""); }, }; } }); parser.addLeafExpression("number", function (parser, runtime, tokens) { var number = tokens.matchTokenType("NUMBER"); if (!number) return; var numberToken = number; var value = parseFloat(/** @type {string} */ (number.value)); return { type: "number", value: value, numberToken: numberToken, evaluate: function () { return value; }, }; }); parser.addLeafExpression("idRef", function (parser, runtime, tokens) { var elementId = tokens.matchTokenType("ID_REF"); if (!elementId) return; if (!elementId.value) return; // TODO - unify these two expression types if (elementId.template) { var templateValue = elementId.value.substring(2); var innerTokens = Lexer.tokenize(templateValue); var innerExpression = parser.requireElement("expression", innerTokens); return { type: "idRefTemplate", args: [innerExpression], op: function (context, arg) { return runtime.getRootNode(context.me).getElementById(arg); }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } else { const value = elementId.value.substring(1); return { type: "idRef", css: elementId.value, value: value, evaluate: function (context) { return ( runtime.getRootNode(context.me).getElementById(value) ); }, }; } }); parser.addLeafExpression("classRef", function (parser, runtime, tokens) { var classRef = tokens.matchTokenType("CLASS_REF"); if (!classRef) return; if (!classRef.value) return; // TODO - unify these two expression types if (classRef.template) { var templateValue = classRef.value.substring(2); var innerTokens = Lexer.tokenize(templateValue); var innerExpression = parser.requireElement("expression", innerTokens); return { type: "classRefTemplate", args: [innerExpression], op: function (context, arg) { return new ElementCollection("." + arg, context.me, true) }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } else { const css = classRef.value; return { type: "classRef", css: css, evaluate: function (context) { return new ElementCollection(css, context.me, true) }, }; } }); class TemplatedQueryElementCollection extends ElementCollection { constructor(css, relativeToElement, templateParts) { super(css, relativeToElement); this.templateParts = templateParts; this.elements = templateParts.filter(elt => elt instanceof Element); } get css() { let rv = "", i = 0 for (const val of this.templateParts) { if (val instanceof Element) { rv += "[data-hs-query-id='" + i++ + "']"; } else rv += val; } return rv; } [Symbol.iterator]() { this.elements.forEach((el, i) => el.dataset.hsQueryId = i); const rv = super[Symbol.iterator](); this.elements.forEach(el => el.removeAttribute('data-hs-query-id')); return rv; } } parser.addLeafExpression("queryRef", function (parser, runtime, tokens) { var queryStart = tokens.matchOpToken("<"); if (!queryStart) return; var queryTokens = tokens.consumeUntil("/"); tokens.requireOpToken("/"); tokens.requireOpToken(">"); var queryValue = queryTokens .map(function (t) { if (t.type === "STRING") { return '"' + t.value + '"'; } else { return t.value; } }) .join(""); var template, innerTokens, args; if (queryValue.indexOf("$") >= 0) { template = true; innerTokens = Lexer.tokenize(queryValue, true); args = parser.parseStringTemplate(innerTokens); } return { type: "queryRef", css: queryValue, args: args, op: function (context, ...args) { if (template) { return new TemplatedQueryElementCollection(queryValue, context.me, args) } else { return new ElementCollection(queryValue, context.me) } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; }); parser.addLeafExpression("attributeRef", function (parser, runtime, tokens) { var attributeRef = tokens.matchTokenType("ATTRIBUTE_REF"); if (!attributeRef) return; if (!attributeRef.value) return; var outerVal = attributeRef.value; if (outerVal.indexOf("[") === 0) { var innerValue = outerVal.substring(2, outerVal.length - 1); } else { var innerValue = outerVal.substring(1); } var css = "[" + innerValue + "]"; var split = innerValue.split("="); var name = split[0]; var value = split[1]; if (value) { // strip quotes if (value.indexOf('"') === 0) { value = value.substring(1, value.length - 1); } } return { type: "attributeRef", name: name, css: css, value: value, op: function (context) { var target = context.you || context.me; if (target) { return target.getAttribute(name); } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; }); parser.addLeafExpression("styleRef", function (parser, runtime, tokens) { var styleRef = tokens.matchTokenType("STYLE_REF"); if (!styleRef) return; if (!styleRef.value) return; var styleProp = styleRef.value.substr(1); if (styleProp.startsWith("computed-")) { styleProp = styleProp.substr("computed-".length); return { type: "computedStyleRef", name: styleProp, op: function (context) { var target = context.you || context.me; if (target) { return runtime.resolveComputedStyle(target, styleProp); } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } else { return { type: "styleRef", name: styleProp, op: function (context) { var target = context.you || context.me; if (target) { return runtime.resolveStyle(target, styleProp); } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } }); parser.addGrammarElement("objectKey", function (parser, runtime, tokens) { var token; if ((token = tokens.matchTokenType("STRING"))) { return { type: "objectKey", key: token.value, evaluate: function () { return token.value; }, }; } else if (tokens.matchOpToken("[")) { var expr = parser.parseElement("expression", tokens); tokens.requireOpToken("]"); return { type: "objectKey", expr: expr, args: [expr], op: function (ctx, expr) { return expr; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } else { var key = ""; do { token = tokens.matchTokenType("IDENTIFIER") || tokens.matchOpToken("-"); if (token) key += token.value; } while (token); return { type: "objectKey", key: key, evaluate: function () { return key; }, }; } }); parser.addLeafExpression("objectLiteral", function (parser, runtime, tokens) { if (!tokens.matchOpToken("{")) return; var keyExpressions = []; var valueExpressions = []; if (!tokens.matchOpToken("}")) { do { var name = parser.requireElement("objectKey", tokens); tokens.requireOpToken(":"); var value = parser.requireElement("expression", tokens); valueExpressions.push(value); keyExpressions.push(name); } while (tokens.matchOpToken(",")); tokens.requireOpToken("}"); } return { type: "objectLiteral", args: [keyExpressions, valueExpressions], op: function (context, keys, values) { var returnVal = {}; for (var i = 0; i < keys.length; i++) { returnVal[keys[i]] = values[i]; } return returnVal; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; }); parser.addGrammarElement("nakedNamedArgumentList", function (parser, runtime, tokens) { var fields = []; var valueExpressions = []; if (tokens.currentToken().type === "IDENTIFIER") { do { var name = tokens.requireTokenType("IDENTIFIER"); tokens.requireOpToken(":"); var value = parser.requireElement("expression", tokens); valueExpressions.push(value); fields.push({ name: name, value: value }); } while (tokens.matchOpToken(",")); } return { type: "namedArgumentList", fields: fields, args: [valueExpressions], op: function (context, values) { var returnVal = { _namedArgList_: true }; for (var i = 0; i < values.length; i++) { var field = fields[i]; returnVal[field.name.value] = values[i]; } return returnVal; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; }); parser.addGrammarElement("namedArgumentList", function (parser, runtime, tokens) { if (!tokens.matchOpToken("(")) return; var elt = parser.requireElement("nakedNamedArgumentList", tokens); tokens.requireOpToken(")"); return elt; }); parser.addGrammarElement("symbol", function (parser, runtime, tokens) { /** @scope {SymbolScope} */ var scope = "default"; if (tokens.matchToken("global")) { scope = "global"; } else if (tokens.matchToken("element") || tokens.matchToken("module")) { scope = "element"; // optional possessive if (tokens.matchOpToken("'")) { tokens.requireToken("s"); } } else if (tokens.matchToken("local")) { scope = "local"; } // TODO better look ahead here let eltPrefix = tokens.matchOpToken(":"); let identifier = tokens.matchTokenType("IDENTIFIER"); if (identifier && identifier.value) { var name = identifier.value; if (eltPrefix) { name = ":" + name; } if (scope === "default") { if (name.indexOf("$") === 0) { scope = "global"; } if (name.indexOf(":") === 0) { scope = "element"; } } return { type: "symbol", token: identifier, scope: scope, name: name, evaluate: function (context) { return runtime.resolveSymbol(name, context, scope); }, }; } }); parser.addGrammarElement("implicitMeTarget", function (parser, runtime, tokens) { return { type: "implicitMeTarget", evaluate: function (context) { return context.you || context.me; }, }; }); parser.addLeafExpression("boolean", function (parser, runtime, tokens) { var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false"); if (!booleanLiteral) return; const value = booleanLiteral.value === "true"; return { type: "boolean", evaluate: function (context) { return value; }, }; }); parser.addLeafExpression("null", function (parser, runtime, tokens) { if (tokens.matchToken("null")) { return { type: "null", evaluate: function (context) { return null; }, }; } }); parser.addLeafExpression("arrayLiteral", function (parser, runtime, tokens) { if (!tokens.matchOpToken("[")) return; var values = []; if (!tokens.matchOpToken("]")) { do { var expr = parser.requireElement("expression", tokens); values.push(expr); } while (tokens.matchOpToken(",")); tokens.requireOpToken("]"); } return { type: "arrayLiteral", values: values, args: [values], op: function (context, values) { return values; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; }); parser.addLeafExpression("blockLiteral", function (parser, runtime, tokens) { if (!tokens.matchOpToken("\\")) return; var args = []; var arg1 = tokens.matchTokenType("IDENTIFIER"); if (arg1) { args.push(arg1); while (tokens.matchOpToken(",")) { args.push(tokens.requireTokenType("IDENTIFIER")); } } // TODO compound op token tokens.requireOpToken("-"); tokens.requireOpToken(">"); var expr = parser.requireElement("expression", tokens); return { type: "blockLiteral", args: args, expr: expr, evaluate: function (ctx) { var returnFunc = function () { //TODO - push scope for (var i = 0; i < args.length; i++) { ctx.locals[args[i].value] = arguments[i]; } return expr.evaluate(ctx); //OK }; return returnFunc; }, }; }); parser.addIndirectExpression("propertyAccess", function (parser, runtime, tokens, root) { if (!tokens.matchOpToken(".")) return; var prop = tokens.requireTokenType("IDENTIFIER"); var propertyAccess = { type: "propertyAccess", root: root, prop: prop, args: [root], op: function (_context, rootVal) { var value = runtime.resolveProperty(rootVal, prop.value); return value; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; return parser.parseElement("indirectExpression", tokens, propertyAccess); }); parser.addIndirectExpression("of", function (parser, runtime, tokens, root) { if (!tokens.matchToken("of")) return; var newRoot = parser.requireElement("unaryExpression", tokens); // find the urroot var childOfUrRoot = null; var urRoot = root; while (urRoot.root) { childOfUrRoot = urRoot; urRoot = urRoot.root; } if (urRoot.type !== "symbol" && urRoot.type !== "attributeRef" && urRoot.type !== "styleRef" && urRoot.type !== "computedStyleRef") { parser.raiseParseError(tokens, "Cannot take a property of a non-symbol: " + urRoot.type); } var attribute = urRoot.type === "attributeRef"; var style = urRoot.type === "styleRef" || urRoot.type === "computedStyleRef"; if (attribute || style) { var attributeElt = urRoot } var prop = urRoot.name; var propertyAccess = { type: "ofExpression", prop: urRoot.token, root: newRoot, attribute: attributeElt, expression: root, args: [newRoot], op: function (context, rootVal) { if (attribute) { return runtime.resolveAttribute(rootVal, prop); } else if (style) { if (urRoot.type === "computedStyleRef") { return runtime.resolveComputedStyle(rootVal, prop); } else { return runtime.resolveStyle(rootVal, prop); } } else { return runtime.resolveProperty(rootVal, prop); } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; if (urRoot.type === "attributeRef") { propertyAccess.attribute = urRoot; } if (childOfUrRoot) { childOfUrRoot.root = propertyAccess; childOfUrRoot.args = [propertyAccess]; } else { root = propertyAccess; } return parser.parseElement("indirectExpression", tokens, root); }); parser.addIndirectExpression("possessive", function (parser, runtime, tokens, root) { if (parser.possessivesDisabled) { return; } var apostrophe = tokens.matchOpToken("'"); if ( apostrophe || (root.type === "symbol" && (root.name === "my" || root.name === "its" || root.name === "your") && (tokens.currentToken().type === "IDENTIFIER" || tokens.currentToken().type === "ATTRIBUTE_REF" || tokens.currentToken().type === "STYLE_REF")) ) { if (apostrophe) { tokens.requireToken("s"); } var attribute, style, prop; attribute = parser.parseElement("attributeRef", tokens); if (attribute == null) { style = parser.parseElement("styleRef", tokens); if (style == null) { prop = tokens.requireTokenType("IDENTIFIER"); } } var propertyAccess = { type: "possessive", root: root, attribute: attribute || style, prop: prop, args: [root], op: function (context, rootVal) { if (attribute) { // @ts-ignore var value = runtime.resolveAttribute(rootVal, attribute.name); } else if (style) { var value if (style.type === 'computedStyleRef') { value = runtime.resolveComputedStyle(rootVal, style['name']); } else { value = runtime.resolveStyle(rootVal, style['name']); } } else { var value = runtime.resolveProperty(rootVal, prop.value); } return value; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; return parser.parseElement("indirectExpression", tokens, propertyAccess); } }); parser.addIndirectExpression("inExpression", function (parser, runtime, tokens, root) { if (!tokens.matchToken("in")) return; var target = parser.requireElement("unaryExpression", tokens); var propertyAccess = { type: "inExpression", root: root, args: [root, target], op: function (context, rootVal, target) { var returnArr = []; if (rootVal.css) { runtime.implicitLoop(target, function (targetElt) { var results = targetElt.querySelectorAll(rootVal.css); for (var i = 0; i < results.length; i++) { returnArr.push(results[i]); } }); } else if (rootVal instanceof Element) { var within = false; runtime.implicitLoop(target, function (targetElt) { if (targetElt.contains(rootVal)) { within = true; } }); if(within) { return rootVal; } } else { runtime.implicitLoop(rootVal, function (rootElt) { runtime.implicitLoop(target, function (targetElt) { if (rootElt === targetElt) { returnArr.push(rootElt); } }); }); } return returnArr; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; return parser.parseElement("indirectExpression", tokens, propertyAccess); }); parser.addIndirectExpression("asExpression", function (parser, runtime, tokens, root) { if (!tokens.matchToken("as")) return; tokens.matchToken("a") || tokens.matchToken("an"); var conversion = parser.requireElement("dotOrColonPath", tokens).evaluate(); // OK No promise var propertyAccess = { type: "asExpression", root: root, args: [root], op: function (context, rootVal) { return runtime.convertValue(rootVal, conversion); }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; return parser.parseElement("indirectExpression", tokens, propertyAccess); }); parser.addIndirectExpression("functionCall", function (parser, runtime, tokens, root) { if (!tokens.matchOpToken("(")) return; var args = []; if (!tokens.matchOpToken(")")) { do { args.push(parser.requireElement("expression", tokens)); } while (tokens.matchOpToken(",")); tokens.requireOpToken(")"); } if (root.root) { var functionCall = { type: "functionCall", root: root, argExressions: args, args: [root.root, args], op: function (context, rootRoot, args) { runtime.nullCheck(rootRoot, root.root); var func = rootRoot[root.prop.value]; runtime.nullCheck(func, root); if (func.hyperfunc) { args.push(context); } return func.apply(rootRoot, args); }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } else { var functionCall = { type: "functionCall", root: root, argExressions: args, args: [root, args], op: function (context, func, argVals) { runtime.nullCheck(func, root); if (func.hyperfunc) { argVals.push(context); } var apply = func.apply(null, argVals); return apply; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } return parser.parseElement("indirectExpression", tokens, functionCall); }); parser.addIndirectExpression("attributeRefAccess", function (parser, runtime, tokens, root) { var attribute = parser.parseElement("attributeRef", tokens); if (!attribute) return; var attributeAccess = { type: "attributeRefAccess", root: root, attribute: attribute, args: [root], op: function (_ctx, rootVal) { // @ts-ignore var value = runtime.resolveAttribute(rootVal, attribute.name); return value; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; return attributeAccess; }); parser.addIndirectExpression("arrayIndex", function (parser, runtime, tokens, root) { if (!tokens.matchOpToken("[")) return; var andBefore = false; var andAfter = false; var firstIndex = null; var secondIndex = null; if (tokens.matchOpToken("..")) { andBefore = true; firstIndex = parser.requireElement("expression", tokens); } else { firstIndex = parser.requireElement("expression", tokens); if (tokens.matchOpToken("..")) { andAfter = true; var current = tokens.currentToken(); if (current.type !== "R_BRACKET") { secondIndex = parser.parseElement("expression", tokens); } } } tokens.requireOpToken("]"); var arrayIndex = { type: "arrayIndex", root: root, prop: firstIndex, firstIndex: firstIndex, secondIndex: secondIndex, args: [root, firstIndex, secondIndex], op: function (_ctx, root, firstIndex, secondIndex) { if (root == null) { return null; } if (andBefore) { if (firstIndex < 0) { firstIndex = root.length + firstIndex; } return root.slice(0, firstIndex + 1); // returns all items from beginning to firstIndex (inclusive) } else if (andAfter) { if (secondIndex != null) { if (secondIndex < 0) { secondIndex = root.length + secondIndex; } return root.slice(firstIndex, secondIndex + 1); // returns all items from firstIndex to secondIndex (inclusive) } else { return root.slice(firstIndex); // returns from firstIndex to end of array } } else { return root[firstIndex]; } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; return parser.parseElement("indirectExpression", tokens, arrayIndex); }); // taken from https://drafts.csswg.org/css-values-4/#relative-length // and https://drafts.csswg.org/css-values-4/#absolute-length // (NB: we do not support `in` dues to conflicts w/ the hyperscript grammar) var STRING_POSTFIXES = [ 'em', 'ex', 'cap', 'ch', 'ic', 'rem', 'lh', 'rlh', 'vw', 'vh', 'vi', 'vb', 'vmin', 'vmax', 'cm', 'mm', 'Q', 'pc', 'pt', 'px' ]; parser.addGrammarElement("postfixExpression", function (parser, runtime, tokens) { var root = parser.parseElement("primaryExpression", tokens); let stringPosfix = tokens.matchAnyToken.apply(tokens, STRING_POSTFIXES) || tokens.matchOpToken("%"); if (stringPosfix) { return { type: "stringPostfix", postfix: stringPosfix.value, args: [root], op: function (context, val) { return "" + val + stringPosfix.value; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } var timeFactor = null; if (tokens.matchToken("s") || tokens.matchToken("seconds")) { timeFactor = 1000; } else if (tokens.matchToken("ms") || tokens.matchToken("milliseconds")) { timeFactor = 1; } if (timeFactor) { return { type: "timeExpression", time: root, factor: timeFactor, args: [root], op: function (_context, val) { return val * timeFactor; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } if (tokens.matchOpToken(":")) { var typeName = tokens.requireTokenType("IDENTIFIER"); if (!typeName.value) return; var nullOk = !tokens.matchOpToken("!"); return { type: "typeCheck", typeName: typeName, nullOk: nullOk, args: [root], op: function (context, val) { var passed = runtime.typeCheck(val, this.typeName.value, nullOk); if (passed) { return val; } else { throw new Error("Typecheck failed! Expected: " + typeName.value); } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } else { return root; } }); parser.addGrammarElement("logicalNot", function (parser, runtime, tokens) { if (!tokens.matchToken("not")) return; var root = parser.requireElement("unaryExpression", tokens); return { type: "logicalNot", root: root, args: [root], op: function (context, val) { return !val; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; }); parser.addGrammarElement("noExpression", function (parser, runtime, tokens) { if (!tokens.matchToken("no")) return; var root = parser.requireElement("unaryExpression", tokens); return { type: "noExpression", root: root, args: [root], op: function (_context, val) { return runtime.isEmpty(val); }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; }); parser.addLeafExpression("some", function (parser, runtime, tokens) { if (!tokens.matchToken("some")) return; var root = parser.requireElement("expression", tokens); return { type: "noExpression", root: root, args: [root], op: function (_context, val) { return !runtime.isEmpty(val); }, evaluate(context) { return runtime.unifiedEval(this, context); }, }; }); parser.addGrammarElement("negativeNumber", function (parser, runtime, tokens) { if (!tokens.matchOpToken("-")) return; var root = parser.requireElement("unaryExpression", tokens); return { type: "negativeNumber", root: root, args: [root], op: function (context, value) { return -1 * value; }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; }); parser.addGrammarElement("unaryExpression", function (parser, runtime, tokens) { tokens.matchToken("the"); // optional "the" return parser.parseAnyOf( ["beepExpression", "logicalNot", "relativePositionalExpression", "positionalExpression", "noExpression", "negativeNumber", "postfixExpression"], tokens ); }); parser.addGrammarElement("beepExpression", function (parser, runtime, tokens) { if (!tokens.matchToken("beep!")) return; var expression = parser.parseElement("unaryExpression", tokens); if (expression) { expression['booped'] = true; var originalEvaluate = expression.evaluate; expression.evaluate = function(ctx){ let value = originalEvaluate.apply(expression, arguments); let element = ctx.me; runtime.beepValueToConsole(element, expression, value); return value; } return expression; } }); var scanForwardQuery = function(start, root, match, wrap) { var results = root.querySelectorAll(match); for (var i = 0; i < results.length; i++) { var elt = results[i]; if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_PRECEDING) { return elt; } } if (wrap) { return results[0]; } } var scanBackwardsQuery = function(start, root, match, wrap) { var results = root.querySelectorAll(match); for (var i = results.length - 1; i >= 0; i--) { var elt = results[i]; if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_FOLLOWING) { return elt; } } if (wrap) { return results[results.length - 1]; } } var scanForwardArray = function(start, array, match, wrap) { var matches = []; Runtime.prototype.forEach(array, function(elt){ if (elt.matches(match) || elt === start) { matches.push(elt); } }) for (var i = 0; i < matches.length - 1; i++) { var elt = matches[i]; if (elt === start) { return matches[i + 1]; } } if (wrap) { var first = matches[0]; if (first && first.matches(match)) { return first; } } } var scanBackwardsArray = function(start, array, match, wrap) { return scanForwardArray(start, Array.from(array).reverse(), match, wrap); } parser.addGrammarElement("relativePositionalExpression", function (parser, runtime, tokens) { var op = tokens.matchAnyToken("next", "previous"); if (!op) return; var forwardSearch = op.value === "next"; var thingElt = parser.parseElement("expression", tokens); if (tokens.matchToken("from")) { tokens.pushFollow("in"); try { var from = parser.requireElement("unaryExpression", tokens); } finally { tokens.popFollow(); } } else { var from = parser.requireElement("implicitMeTarget", tokens); } var inSearch = false; var withinElt; if (tokens.matchToken("in")) { inSearch = true; var inElt = parser.requireElement("unaryExpression", tokens); } else if (tokens.matchToken("within")) { withinElt = parser.requireElement("unaryExpression", tokens); } else { withinElt = document.body; } var wrapping = false; if (tokens.matchToken("with")) { tokens.requireToken("wrapping") wrapping = true; } return { type: "relativePositionalExpression", from: from, forwardSearch: forwardSearch, inSearch: inSearch, wrapping: wrapping, inElt: inElt, withinElt: withinElt, operator: op.value, args: [thingElt, from, inElt, withinElt], op: function (context, thing, from, inElt, withinElt) { var css = thing.css; if (css == null) { throw "Expected a CSS value to be returned by " + Tokens.sourceFor.apply(thingElt); } if(inSearch) { if (inElt) { if (forwardSearch) { return scanForwardArray(from, inElt, css, wrapping); } else { return scanBackwardsArray(from, inElt, css, wrapping); } } } else { if (withinElt) { if (forwardSearch) { return scanForwardQuery(from, withinElt, css, wrapping); } else { return scanBackwardsQuery(from, withinElt, css, wrapping); } } } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, } }); parser.addGrammarElement("positionalExpression", function (parser, runtime, tokens) { var op = tokens.matchAnyToken("first", "last", "random"); if (!op) return; tokens.matchAnyToken("in", "from", "of"); var rhs = parser.requireElement("unaryExpression", tokens); const operator = op.value; return { type: "positionalExpression", rhs: rhs, operator: op.value, args: [rhs], op: function (context, rhsVal) { if (rhsVal && !Array.isArray(rhsVal)) { if (rhsVal.children) { rhsVal = rhsVal.children; } else { rhsVal = Array.from(rhsVal); } } if (rhsVal) { if (operator === "first") { return rhsVal[0]; } else if (operator === "last") { return rhsVal[rhsVal.length - 1]; } else if (operator === "random") { return rhsVal[Math.floor(Math.random() * rhsVal.length)]; } } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; }); parser.addGrammarElement("mathOperator", function (parser, runtime, tokens) { var expr = parser.parseElement("unaryExpression", tokens); var mathOp, initialMathOp = null; mathOp = tokens.matchAnyOpToken("+", "-", "*", "/") || tokens.matchToken('mod'); while (mathOp) { initialMathOp = initialMathOp || mathOp; var operator = mathOp.value; if (initialMathOp.value !== operator) { parser.raiseParseError(tokens, "You must parenthesize math operations with different operators"); } var rhs = parser.parseElement("unaryExpression", tokens); expr = { type: "mathOperator", lhs: expr, rhs: rhs, operator: operator, args: [expr, rhs], op: function (context, lhsVal, rhsVal) { if (operator === "+") { return lhsVal + rhsVal; } else if (operator === "-") { return lhsVal - rhsVal; } else if (operator === "*") { return lhsVal * rhsVal; } else if (operator === "/") { return lhsVal / rhsVal; } else if (operator === "mod") { return lhsVal % rhsVal; } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; mathOp = tokens.matchAnyOpToken("+", "-", "*", "/") || tokens.matchToken('mod'); } return expr; }); parser.addGrammarElement("mathExpression", function (parser, runtime, tokens) { return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens); }); function sloppyContains(src, container, value){ if (container['contains']) { return container.contains(value); } else if (container['includes']) { return container.includes(value); } else { throw Error("The value of " + src.sourceFor() + " does not have a contains or includes method on it"); } } function sloppyMatches(src, target, toMatch){ if (target['match']) { return !!target.match(toMatch); } else if (target['matches']) { return target.matches(toMatch); } else { throw Error("The value of " + src.sourceFor() + " does not have a match or matches method on it"); } } parser.addGrammarElement("comparisonOperator", function (parser, runtime, tokens) { var expr = parser.parseElement("mathExpression", tokens); var comparisonToken = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!=="); var operator = comparisonToken ? comparisonToken.value : null; var hasRightValue = true; // By default, most comparisons require two values, but there are some exceptions. var typeCheck = false; if (operator == null) { if (tokens.matchToken("is") || tokens.matchToken("am")) { if (tokens.matchToken("not")) { if (tokens.matchToken("in")) { operator = "not in"; } else if (tokens.matchToken("a")) { operator = "not a"; typeCheck = true; } else if (tokens.matchToken("empty")) { operator = "not empty"; hasRightValue = false; } else { if (tokens.matchToken("really")) { operator = "!=="; } else { operator = "!="; } // consume additional optional syntax if (tokens.matchToken("equal")) { tokens.matchToken("to"); } } } else if (tokens.matchToken("in")) { operator = "in"; } else if (tokens.matchToken("a")) { operator = "a"; typeCheck = true; } else if (tokens.matchToken("empty")) { operator = "empty"; hasRightValue = false; } else if (tokens.matchToken("less")) { tokens.requireToken("than"); if (tokens.matchToken("or")) { tokens.requireToken("equal"); tokens.requireToken("to"); operator = "<="; } else { operator = "<"; } } else if (tokens.matchToken("greater")) { tokens.requireToken("than"); if (tokens.matchToken("or")) { tokens.requireToken("equal"); tokens.requireToken("to"); operator = ">="; } else { operator = ">"; } } else { if (tokens.matchToken("really")) { operator = "==="; } else { operator = "=="; } if (tokens.matchToken("equal")) { tokens.matchToken("to"); } } } else if (tokens.matchToken("equals")) { operator = "=="; } else if (tokens.matchToken("really")) { tokens.requireToken("equals") operator = "==="; } else if (tokens.matchToken("exist") || tokens.matchToken("exists")) { operator = "exist"; hasRightValue = false; } else if (tokens.matchToken("matches") || tokens.matchToken("match")) { operator = "match"; } else if (tokens.matchToken("contains") || tokens.matchToken("contain")) { operator = "contain"; } else if (tokens.matchToken("includes") || tokens.matchToken("include")) { operator = "include"; } else if (tokens.matchToken("do") || tokens.matchToken("does")) { tokens.requireToken("not"); if (tokens.matchToken("matches") || tokens.matchToken("match")) { operator = "not match"; } else if (tokens.matchToken("contains") || tokens.matchToken("contain")) { operator = "not contain"; } else if (tokens.matchToken("exist") || tokens.matchToken("exist")) { operator = "not exist"; hasRightValue = false; } else if (tokens.matchToken("include")) { operator = "not include"; } else { parser.raiseParseError(tokens, "Expected matches or contains"); } } } if (operator) { // Do not allow chained comparisons, which is dumb var typeName, nullOk, rhs if (typeCheck) { typeName = tokens.requireTokenType("IDENTIFIER"); nullOk = !tokens.matchOpToken("!"); } else if (hasRightValue) { rhs = parser.requireElement("mathExpression", tokens); if (operator === "match" || operator === "not match") { rhs = rhs.css ? rhs.css : rhs; } } var lhs = expr; expr = { type: "comparisonOperator", operator: operator, typeName: typeName, nullOk: nullOk, lhs: expr, rhs: rhs, args: [expr, rhs], op: function (context, lhsVal, rhsVal) { if (operator === "==") { return lhsVal == rhsVal; } else if (operator === "!=") { return lhsVal != rhsVal; } if (operator === "===") { return lhsVal === rhsVal; } else if (operator === "!==") { return lhsVal !== rhsVal; } if (operator === "match") { return lhsVal != null && sloppyMatches(lhs, lhsVal, rhsVal); } if (operator === "not match") { return lhsVal == null || !sloppyMatches(lhs, lhsVal, rhsVal); } if (operator === "in") { return rhsVal != null && sloppyContains(rhs, rhsVal, lhsVal); } if (operator === "not in") { return rhsVal == null || !sloppyContains(rhs, rhsVal, lhsVal); } if (operator === "contain") { return lhsVal != null && sloppyContains(lhs, lhsVal, rhsVal); } if (operator === "not contain") { return lhsVal == null || !sloppyContains(lhs, lhsVal, rhsVal); } if (operator === "include") { return lhsVal != null && sloppyContains(lhs, lhsVal, rhsVal); } if (operator === "not include") { return lhsVal == null || !sloppyContains(lhs, lhsVal, rhsVal); } if (operator === "===") { return lhsVal === rhsVal; } else if (operator === "!==") { return lhsVal !== rhsVal; } else if (operator === "<") { return lhsVal < rhsVal; } else if (operator === ">") { return lhsVal > rhsVal; } else if (operator === "<=") { return lhsVal <= rhsVal; } else if (operator === ">=") { return lhsVal >= rhsVal; } else if (operator === "empty") { return runtime.isEmpty(lhsVal); } else if (operator === "not empty") { return !runtime.isEmpty(lhsVal); } else if (operator === "exist") { return runtime.doesExist(lhsVal); } else if (operator === "not exist") { return !runtime.doesExist(lhsVal); } else if (operator === "a") { return runtime.typeCheck(lhsVal, typeName.value, nullOk); } else if (operator === "not a") { return !runtime.typeCheck(lhsVal, typeName.value, nullOk); } else { throw "Unknown comparison : " + operator; } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; } return expr; }); parser.addGrammarElement("comparisonExpression", function (parser, runtime, tokens) { return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens); }); parser.addGrammarElement("logicalOperator", function (parser, runtime, tokens) { var expr = parser.parseElement("comparisonExpression", tokens); var logicalOp, initialLogicalOp = null; logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); while (logicalOp) { initialLogicalOp = initialLogicalOp || logicalOp; if (initialLogicalOp.value !== logicalOp.value) { parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators"); } var rhs = parser.requireElement("comparisonExpression", tokens); const operator = logicalOp.value; expr = { type: "logicalOperator", operator: operator, lhs: expr, rhs: rhs, args: [expr, rhs], op: function (context, lhsVal, rhsVal) { if (operator === "and") { return lhsVal && rhsVal; } else { return lhsVal || rhsVal; } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); } return expr; }); parser.addGrammarElement("logicalExpression", function (parser, runtime, tokens) { return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens); }); parser.addGrammarElement("asyncExpression", function (parser, runtime, tokens) { if (tokens.matchToken("async")) { var value = parser.requireElement("logicalExpression", tokens); var expr = { type: "asyncExpression", value: value, evaluate: function (context) { return { asyncWrapper: true, value: this.value.evaluate(context), //OK }; }, }; return expr; } else { return parser.parseElement("logicalExpression", tokens); } }); parser.addGrammarElement("expression", function (parser, runtime, tokens) { tokens.matchToken("the"); // optional the return parser.parseElement("asyncExpression", tokens); }); parser.addGrammarElement("assignableExpression", function (parser, runtime, tokens) { tokens.matchToken("the"); // optional the // TODO obviously we need to generalize this as a left hand side / targetable concept var expr = parser.parseElement("primaryExpression", tokens); if (expr && ( expr.type === "symbol" || expr.type === "ofExpression" || expr.type === "propertyAccess" || expr.type === "attributeRefAccess" || expr.type === "attributeRef" || expr.type === "styleRef" || expr.type === "arrayIndex" || expr.type === "possessive") ) { return expr; } else { parser.raiseParseError( tokens, "A target expression must be writable. The expression type '" + (expr && expr.type) + "' is not." ); } return expr; }); parser.addGrammarElement("hyperscript", function (parser, runtime, tokens) { var features = []; if (tokens.hasMore()) { while (parser.featureStart(tokens.currentToken()) || tokens.currentToken().value === "(") { var feature = parser.requireElement("feature", tokens); features.push(feature); tokens.matchToken("end"); // optional end } } return { type: "hyperscript", features: features, apply: function (target, source, args) { // no op for (const feature of features) { feature.install(target, source, args); } }, }; }); var parseEventArgs = function (tokens) { var args = []; // handle argument list (look ahead 3) if ( tokens.token(0).value === "(" && (tokens.token(1).value === ")" || tokens.token(2).value === "," || tokens.token(2).value === ")") ) { tokens.matchOpToken("("); do { args.push(tokens.requireTokenType("IDENTIFIER")); } while (tokens.matchOpToken(",")); tokens.requireOpToken(")"); } return args; }; parser.addFeature("on", function (parser, runtime, tokens) { if (!tokens.matchToken("on")) return; var every = false; if (tokens.matchToken("every")) { every = true; } var events = []; var displayName = null; do { var on = parser.requireElement("eventName", tokens, "Expected event name"); var eventName = on.evaluate(); // OK No Promise if (displayName) { displayName = displayName + " or " + eventName; } else { displayName = "on " + eventName; } var args = parseEventArgs(tokens); var filter = null; if (tokens.matchOpToken("[")) { filter = parser.requireElement("expression", tokens); tokens.requireOpToken("]"); } var startCount, endCount ,unbounded; if (tokens.currentToken().type === "NUMBER") { var startCountToken = tokens.consumeToken(); if (!startCountToken.value) return; startCount = parseInt(startCountToken.value); if (tokens.matchToken("to")) { var endCountToken = tokens.consumeToken(); if (!endCountToken.value) return; endCount = parseInt(endCountToken.value); } else if (tokens.matchToken("and")) { unbounded = true; tokens.requireToken("on"); } } var intersectionSpec, mutationSpec; if (eventName === "intersection") { intersectionSpec = {}; if (tokens.matchToken("with")) { intersectionSpec["with"] = parser.requireElement("expression", tokens).evaluate(); } if (tokens.matchToken("having")) { do { if (tokens.matchToken("margin")) { intersectionSpec["rootMargin"] = parser.requireElement("stringLike", tokens).evaluate(); } else if (tokens.matchToken("threshold")) { intersectionSpec["threshold"] = parser.requireElement("expression", tokens).evaluate(); } else { parser.raiseParseError(tokens, "Unknown intersection config specification"); } } while (tokens.matchToken("and")); } } else if (eventName === "mutation") { mutationSpec = {}; if (tokens.matchToken("of")) { do { if (tokens.matchToken("anything")) { mutationSpec["attributes"] = true; mutationSpec["subtree"] = true; mutationSpec["characterData"] = true; mutationSpec["childList"] = true; } else if (tokens.matchToken("childList")) { mutationSpec["childList"] = true; } else if (tokens.matchToken("attributes")) { mutationSpec["attributes"] = true; mutationSpec["attributeOldValue"] = true; } else if (tokens.matchToken("subtree")) { mutationSpec["subtree"] = true; } else if (tokens.matchToken("characterData")) { mutationSpec["characterData"] = true; mutationSpec["characterDataOldValue"] = true; } else if (tokens.currentToken().type === "ATTRIBUTE_REF") { var attribute = tokens.consumeToken(); if (mutationSpec["attributeFilter"] == null) { mutationSpec["attributeFilter"] = []; } if (attribute.value.indexOf("@") == 0) { mutationSpec["attributeFilter"].push(attribute.value.substring(1)); } else { parser.raiseParseError( tokens, "Only shorthand attribute references are allowed here" ); } } else { parser.raiseParseError(tokens, "Unknown mutation config specification"); } } while (tokens.matchToken("or")); } else { mutationSpec["attributes"] = true; mutationSpec["characterData"] = true; mutationSpec["childList"] = true; } } var from = null; var elsewhere = false; if (tokens.matchToken("from")) { if (tokens.matchToken("elsewhere")) { elsewhere = true; } else { tokens.pushFollow("or"); try { from = parser.requireElement("expression", tokens) } finally { tokens.popFollow(); } if (!from) { parser.raiseParseError(tokens, 'Expected either target value or "elsewhere".'); } } } // support both "elsewhere" and "from elsewhere" if (from === null && elsewhere === false && tokens.matchToken("elsewhere")) { elsewhere = true; } if (tokens.matchToken("in")) { var inExpr = parser.parseElement('unaryExpression', tokens); } if (tokens.matchToken("debounced")) { tokens.requireToken("at"); var timeExpr = parser.requireElement("unaryExpression", tokens); // @ts-ignore var debounceTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr } else if (tokens.matchToken("throttled")) { tokens.requireToken("at"); var timeExpr = parser.requireElement("unaryExpression", tokens); // @ts-ignore var throttleTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr } events.push({ execCount: 0, every: every, on: eventName, args: args, filter: filter, from: from, inExpr: inExpr, elsewhere: elsewhere, startCount: startCount, endCount: endCount, unbounded: unbounded, debounceTime: debounceTime, throttleTime: throttleTime, mutationSpec: mutationSpec, intersectionSpec: intersectionSpec, debounced: undefined, lastExec: undefined, }); } while (tokens.matchToken("or")); var queueLast = true; if (!every) { if (tokens.matchToken("queue")) { if (tokens.matchToken("all")) { var queueAll = true; var queueLast = false; } else if (tokens.matchToken("first")) { var queueFirst = true; } else if (tokens.matchToken("none")) { var queueNone = true; } else { tokens.requireToken("last"); } } } var start = parser.requireElement("commandList", tokens); parser.ensureTerminated(start); var errorSymbol, errorHandler; if (tokens.matchToken("catch")) { errorSymbol = tokens.requireTokenType("IDENTIFIER").value; errorHandler = parser.requireElement("commandList", tokens); parser.ensureTerminated(errorHandler); } if (tokens.matchToken("finally")) { var finallyHandler = parser.requireElement("commandList", tokens); parser.ensureTerminated(finallyHandler); } var onFeature = { displayName: displayName, events: events, start: start, every: every, execCount: 0, errorHandler: errorHandler, errorSymbol: errorSymbol, execute: function (/** @type {Context} */ ctx) { let eventQueueInfo = runtime.getEventQueueFor(ctx.me, onFeature); if (eventQueueInfo.executing && every === false) { if (queueNone || (queueFirst && eventQueueInfo.queue.length > 0)) { return; } if (queueLast) { eventQueueInfo.queue.length = 0; } eventQueueInfo.queue.push(ctx); return; } onFeature.execCount++; eventQueueInfo.executing = true; ctx.meta.onHalt = function () { eventQueueInfo.executing = false; var queued = eventQueueInfo.queue.shift(); if (queued) { setTimeout(function () { onFeature.execute(queued); }, 1); } }; ctx.meta.reject = function (err) { console.error(err.message ? err.message : err); var hypertrace = runtime.getHyperTrace(ctx, err); if (hypertrace) { hypertrace.print(); } runtime.triggerEvent(ctx.me, "exception", { error: err, }); }; start.execute(ctx); }, install: function (elt, source) { for (const eventSpec of onFeature.events) { var targets; if (eventSpec.elsewhere) { targets = [document]; } else if (eventSpec.from) { targets = eventSpec.from.evaluate(runtime.makeContext(elt, onFeature, elt, null)); } else { targets = [elt]; } runtime.implicitLoop(targets, function (target) { // OK NO PROMISE var eventName = eventSpec.on; if (target == null) { console.warn("'%s' feature ignored because target does not exists:", displayName, elt); return; } if (eventSpec.mutationSpec) { eventName = "hyperscript:mutation"; const observer = new MutationObserver(function (mutationList, observer) { if (!onFeature.executing) { runtime.triggerEvent(target, eventName, { mutationList: mutationList, observer: observer, }); } }); observer.observe(target, eventSpec.mutationSpec); } if (eventSpec.intersectionSpec) { eventName = "hyperscript:intersection"; const observer = new IntersectionObserver(function (entries) { for (const entry of entries) { var detail = { observer: observer, }; detail = Object.assign(detail, entry); detail["intersecting"] = entry.isIntersecting; runtime.triggerEvent(target, eventName, detail); } }, eventSpec.intersectionSpec); observer.observe(target); } var addEventListener = target.addEventListener || target.on; addEventListener.call(target, eventName, function listener(evt) { // OK NO PROMISE if (typeof Node !== 'undefined' && elt instanceof Node && target !== elt && !elt.isConnected) { target.removeEventListener(eventName, listener); return; } var ctx = runtime.makeContext(elt, onFeature, elt, evt); if (eventSpec.elsewhere && elt.contains(evt.target)) { return; } if (eventSpec.from) { ctx.result = target; } // establish context for (const arg of eventSpec.args) { let eventValue = ctx.event[arg.value]; if (eventValue !== undefined) { ctx.locals[arg.value] = eventValue; } else if ('detail' in ctx.event) { ctx.locals[arg.value] = ctx.event['detail'][arg.value]; } } // install error handler if any ctx.meta.errorHandler = errorHandler; ctx.meta.errorSymbol = errorSymbol; ctx.meta.finallyHandler = finallyHandler; // apply filter if (eventSpec.filter) { var initialCtx = ctx.meta.context; ctx.meta.context = ctx.event; try { var value = eventSpec.filter.evaluate(ctx); //OK NO PROMISE if (value) { // match the javascript semantics for if statements } else { return; } } finally { ctx.meta.context = initialCtx; } } if (eventSpec.inExpr) { var inElement = evt.target; while (true) { if (inElement.matches && inElement.matches(eventSpec.inExpr.css)) { ctx.result = inElement; break; } else { inElement = inElement.parentElement; if (inElement == null) { return; // no match found } } } } // verify counts eventSpec.execCount++; if (eventSpec.startCount) { if (eventSpec.endCount) { if ( eventSpec.execCount < eventSpec.startCount || eventSpec.execCount > eventSpec.endCount ) { return; } } else if (eventSpec.unbounded) { if (eventSpec.execCount < eventSpec.startCount) { return; } } else if (eventSpec.execCount !== eventSpec.startCount) { return; } } //debounce if (eventSpec.debounceTime) { if (eventSpec.debounced) { clearTimeout(eventSpec.debounced); } eventSpec.debounced = setTimeout(function () { onFeature.execute(ctx); }, eventSpec.debounceTime); return; } // throttle if (eventSpec.throttleTime) { if ( eventSpec.lastExec && Date.now() < (eventSpec.lastExec + eventSpec.throttleTime) ) { return; } else { eventSpec.lastExec = Date.now(); } } // apply execute onFeature.execute(ctx); }); }); } }, }; parser.setParent(start, onFeature); return onFeature; }); parser.addFeature("def", function (parser, runtime, tokens) { if (!tokens.matchToken("def")) return; var functionName = parser.requireElement("dotOrColonPath", tokens); var nameVal = functionName.evaluate(); // OK var nameSpace = nameVal.split("."); var funcName = nameSpace.pop(); var args = []; if (tokens.matchOpToken("(")) { if (tokens.matchOpToken(")")) { // emtpy args list } else { do { args.push(tokens.requireTokenType("IDENTIFIER")); } while (tokens.matchOpToken(",")); tokens.requireOpToken(")"); } } var start = parser.requireElement("commandList", tokens); var errorSymbol, errorHandler; if (tokens.matchToken("catch")) { errorSymbol = tokens.requireTokenType("IDENTIFIER").value; errorHandler = parser.parseElement("commandList", tokens); } if (tokens.matchToken("finally")) { var finallyHandler = parser.requireElement("commandList", tokens); parser.ensureTerminated(finallyHandler); } var functionFeature = { displayName: funcName + "(" + args .map(function (arg) { return arg.value; }) .join(", ") + ")", name: funcName, args: args, start: start, errorHandler: errorHandler, errorSymbol: errorSymbol, finallyHandler: finallyHandler, install: function (target, source) { var func = function () { // null, worker var ctx = runtime.makeContext(source, functionFeature, target, null); // install error handler if any ctx.meta.errorHandler = errorHandler; ctx.meta.errorSymbol = errorSymbol; ctx.meta.finallyHandler = finallyHandler; for (var i = 0; i < args.length; i++) { var name = args[i]; var argumentVal = arguments[i]; if (name) { ctx.locals[name.value] = argumentVal; } } ctx.meta.caller = arguments[args.length]; if (ctx.meta.caller) { ctx.meta.callingCommand = ctx.meta.caller.meta.command; } var resolve, reject = null; var promise = new Promise(function (theResolve, theReject) { resolve = theResolve; reject = theReject; }); start.execute(ctx); if (ctx.meta.returned) { return ctx.meta.returnValue; } else { ctx.meta.resolve = resolve; ctx.meta.reject = reject; return promise; } }; func.hyperfunc = true; func.hypername = nameVal; runtime.assignToNamespace(target, nameSpace, funcName, func); }, }; parser.ensureTerminated(start); // terminate error handler if any if (errorHandler) { parser.ensureTerminated(errorHandler); } parser.setParent(start, functionFeature); return functionFeature; }); parser.addFeature("set", function (parser, runtime, tokens) { let setCmd = parser.parseElement("setCommand", tokens); if (setCmd) { if (setCmd.target.scope !== "element") { parser.raiseParseError(tokens, "variables declared at the feature level must be element scoped."); } let setFeature = { start: setCmd, install: function (target, source) { setCmd && setCmd.execute(runtime.makeContext(target, setFeature, target, null)); }, }; parser.ensureTerminated(setCmd); return setFeature; } }); parser.addFeature("init", function (parser, runtime, tokens) { if (!tokens.matchToken("init")) return; var immediately = tokens.matchToken("immediately"); var start = parser.requireElement("commandList", tokens); var initFeature = { start: start, install: function (target, source) { let handler = function () { start && start.execute(runtime.makeContext(target, initFeature, target, null)); }; if (immediately) { handler(); } else { setTimeout(handler, 0); } }, }; // terminate body parser.ensureTerminated(start); parser.setParent(start, initFeature); return initFeature; }); parser.addFeature("worker", function (parser, runtime, tokens) { if (tokens.matchToken("worker")) { parser.raiseParseError( tokens, "In order to use the 'worker' feature, include " + "the _hyperscript worker plugin. See " + "https://hyperscript.org/features/worker/ for " + "more info." ); return undefined } }); parser.addFeature("behavior", function (parser, runtime, tokens) { if (!tokens.matchToken("behavior")) return; var path = parser.requireElement("dotOrColonPath", tokens).evaluate(); var nameSpace = path.split("."); var name = nameSpace.pop(); var formalParams = []; if (tokens.matchOpToken("(") && !tokens.matchOpToken(")")) { do { formalParams.push(tokens.requireTokenType("IDENTIFIER").value); } while (tokens.matchOpToken(",")); tokens.requireOpToken(")"); } var hs = parser.requireElement("hyperscript", tokens); for (var i = 0; i < hs.features.length; i++) { var feature = hs.features[i]; feature.behavior = path; } return { install: function (target, source) { runtime.assignToNamespace( globalScope.document && globalScope.document.body, nameSpace, name, function (target, source, innerArgs) { var internalData = runtime.getInternalData(target); var elementScope = getOrInitObject(internalData, path + "Scope"); for (var i = 0; i < formalParams.length; i++) { elementScope[formalParams[i]] = innerArgs[formalParams[i]]; } hs.apply(target, source); } ); }, }; }); parser.addFeature("install", function (parser, runtime, tokens) { if (!tokens.matchToken("install")) return; var behaviorPath = parser.requireElement("dotOrColonPath", tokens).evaluate(); var behaviorNamespace = behaviorPath.split("."); var args = parser.parseElement("namedArgumentList", tokens); var installFeature; return (installFeature = { install: function (target, source) { runtime.unifiedEval( { args: [args], op: function (ctx, args) { var behavior = globalScope; for (var i = 0; i < behaviorNamespace.length; i++) { behavior = behavior[behaviorNamespace[i]]; if (typeof behavior !== "object" && typeof behavior !== "function") throw new Error("No such behavior defined as " + behaviorPath); } if (!(behavior instanceof Function)) throw new Error(behaviorPath + " is not a behavior"); behavior(target, source, args); }, }, runtime.makeContext(target, installFeature, target, null) ); }, }); }); parser.addGrammarElement("jsBody", function (parser, runtime, tokens) { var jsSourceStart = tokens.currentToken().start; var jsLastToken = tokens.currentToken(); var funcNames = []; var funcName = ""; var expectFunctionDeclaration = false; while (tokens.hasMore()) { jsLastToken = tokens.consumeToken(); var peek = tokens.token(0, true); if (peek.type === "IDENTIFIER" && peek.value === "end") { break; } if (expectFunctionDeclaration) { if (jsLastToken.type === "IDENTIFIER" || jsLastToken.type === "NUMBER") { funcName += jsLastToken.value; } else { if (funcName !== "") funcNames.push(funcName); funcName = ""; expectFunctionDeclaration = false; } } else if (jsLastToken.type === "IDENTIFIER" && jsLastToken.value === "function") { expectFunctionDeclaration = true; } } var jsSourceEnd = jsLastToken.end + 1; return { type: "jsBody", exposedFunctionNames: funcNames, jsSource: tokens.source.substring(jsSourceStart, jsSourceEnd), }; }); parser.addFeature("js", function (parser, runtime, tokens) { if (!tokens.matchToken("js")) return; var jsBody = parser.requireElement("jsBody", tokens); var jsSource = jsBody.jsSource + "\nreturn { " + jsBody.exposedFunctionNames .map(function (name) { return name + ":" + name; }) .join(",") + " } "; var func = new Function(jsSource); return { jsSource: jsSource, function: func, exposedFunctionNames: jsBody.exposedFunctionNames, install: function () { Object.assign(globalScope, func()); }, }; }); parser.addCommand("js", function (parser, runtime, tokens) { if (!tokens.matchToken("js")) return; // Parse inputs var inputs = []; if (tokens.matchOpToken("(")) { if (tokens.matchOpToken(")")) { // empty input list } else { do { var inp = tokens.requireTokenType("IDENTIFIER"); inputs.push(inp.value); } while (tokens.matchOpToken(",")); tokens.requireOpToken(")"); } } var jsBody = parser.requireElement("jsBody", tokens); tokens.matchToken("end"); var func = varargConstructor(Function, inputs.concat([jsBody.jsSource])); var command = { jsSource: jsBody.jsSource, function: func, inputs: inputs, op: function (context) { var args = []; inputs.forEach(function (input) { args.push(runtime.resolveSymbol(input, context, 'default')); }); var result = func.apply(globalScope, args); if (result && typeof result.then === "function") { return new Promise(function (resolve) { result.then(function (actualResult) { context.result = actualResult; resolve(runtime.findNext(this, context)); }); }); } else { context.result = result; return runtime.findNext(this, context); } }, }; return command; }); parser.addCommand("async", function (parser, runtime, tokens) { if (!tokens.matchToken("async")) return; if (tokens.matchToken("do")) { var body = parser.requireElement("commandList", tokens); // Append halt var end = body; while (end.next) end = end.next; end.next = runtime.HALT; tokens.requireToken("end"); } else { var body = parser.requireElement("command", tokens); } var command = { body: body, op: function (context) { setTimeout(function () { body.execute(context); }); return runtime.findNext(this, context); }, }; parser.setParent(body, command); return command; }); parser.addCommand("tell", function (parser, runtime, tokens) { var startToken = tokens.currentToken(); if (!tokens.matchToken("tell")) return; var value = parser.requireElement("expression", tokens); var body = parser.requireElement("commandList", tokens); if (tokens.hasMore() && !parser.featureStart(tokens.currentToken())) { tokens.requireToken("end"); } var slot = "tell_" + startToken.start; var tellCmd = { value: value, body: body, args: [value], resolveNext: function (context) { var iterator = context.meta.iterators[slot]; if (iterator.index < iterator.value.length) { context.you = iterator.value[iterator.index++]; return body; } else { // restore original me context.you = iterator.originalYou; if (this.next) { return this.next; } else { return runtime.findNext(this.parent, context); } } }, op: function (context, value) { if (value == null) { value = []; } else if (!(Array.isArray(value) || value instanceof NodeList)) { value = [value]; } context.meta.iterators[slot] = { originalYou: context.you, index: 0, value: value, }; return this.resolveNext(context); }, }; parser.setParent(body, tellCmd); return tellCmd; }); parser.addCommand("wait", function (parser, runtime, tokens) { if (!tokens.matchToken("wait")) return; var command; // wait on event if (tokens.matchToken("for")) { tokens.matchToken("a"); // optional "a" var events = []; do { var lookahead = tokens.token(0); if (lookahead.type === 'NUMBER' || lookahead.type === 'L_PAREN') { events.push({ time: parser.requireElement('expression', tokens).evaluate() // TODO: do we want to allow async here? }) } else { events.push({ name: parser.requireElement("dotOrColonPath", tokens, "Expected event name").evaluate(), args: parseEventArgs(tokens), }); } } while (tokens.matchToken("or")); if (tokens.matchToken("from")) { var on = parser.requireElement("expression", tokens); } // wait on event command = { event: events, on: on, args: [on], op: function (context, on) { var target = on ? on : context.me; if (!(target instanceof EventTarget)) throw new Error("Not a valid event target: " + this.on.sourceFor()); return new Promise((resolve) => { var resolved = false; for (const eventInfo of events) { var listener = (event) => { context.result = event; if (eventInfo.args) { for (const arg of eventInfo.args) { context.locals[arg.value] = event[arg.value] || (event.detail ? event.detail[arg.value] : null); } } if (!resolved) { resolved = true; resolve(runtime.findNext(this, context)); } }; if (eventInfo.name){ target.addEventListener(eventInfo.name, listener, {once: true}); } else if (eventInfo.time != null) { setTimeout(listener, eventInfo.time, eventInfo.time) } } }); }, }; return command; } else { var time; if (tokens.matchToken("a")) { tokens.requireToken("tick"); time = 0; } else { time = parser.requireElement("expression", tokens); } command = { type: "waitCmd", time: time, args: [time], op: function (context, timeValue) { return new Promise((resolve) => { setTimeout(() => { resolve(runtime.findNext(this, context)); }, timeValue); }); }, execute: function (context) { return runtime.unifiedExec(this, context); }, }; return command; } }); // TODO - colon path needs to eventually become part of ruby-style symbols parser.addGrammarElement("dotOrColonPath", function (parser, runtime, tokens) { var root = tokens.matchTokenType("IDENTIFIER"); if (root) { var path = [root.value]; var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":"); if (separator) { do { path.push(tokens.requireTokenType("IDENTIFIER", "NUMBER").value); } while (tokens.matchOpToken(separator.value)); } return { type: "dotOrColonPath", path: path, evaluate: function () { return path.join(separator ? separator.value : ""); }, }; } }); parser.addGrammarElement("eventName", function (parser, runtime, tokens) { var token; if ((token = tokens.matchTokenType("STRING"))) { return { evaluate: function() { return token.value; }, }; } return parser.parseElement("dotOrColonPath", tokens); }); function parseSendCmd(cmdType, parser, runtime, tokens) { var eventName = parser.requireElement("eventName", tokens); var details = parser.parseElement("namedArgumentList", tokens); if ((cmdType === "send" && tokens.matchToken("to")) || (cmdType === "trigger" && tokens.matchToken("on"))) { var toExpr = parser.requireElement("expression", tokens); } else { var toExpr = parser.requireElement("implicitMeTarget", tokens); } var sendCmd = { eventName: eventName, details: details, to: toExpr, args: [toExpr, eventName, details], op: function (context, to, eventName, details) { runtime.nullCheck(to, toExpr); runtime.implicitLoop(to, function (target) { runtime.triggerEvent(target, eventName, details, context.me); }); return runtime.findNext(sendCmd, context); }, }; return sendCmd; } parser.addCommand("trigger", function (parser, runtime, tokens) { if (tokens.matchToken("trigger")) { return parseSendCmd("trigger", parser, runtime, tokens); } }); parser.addCommand("send", function (parser, runtime, tokens) { if (tokens.matchToken("send")) { return parseSendCmd("send", parser, runtime, tokens); } }); var parseReturnFunction = function (parser, runtime, tokens, returnAValue) { if (returnAValue) { if (parser.commandBoundary(tokens.currentToken())) { parser.raiseParseError(tokens, "'return' commands must return a value. If you do not wish to return a value, use 'exit' instead."); } else { var value = parser.requireElement("expression", tokens); } } var returnCmd = { value: value, args: [value], op: function (context, value) { var resolve = context.meta.resolve; context.meta.returned = true; context.meta.returnValue = value; if (resolve) { if (value) { resolve(value); } else { resolve(); } } return runtime.HALT; }, }; return returnCmd; }; parser.addCommand("return", function (parser, runtime, tokens) { if (tokens.matchToken("return")) { return parseReturnFunction(parser, runtime, tokens, true); } }); parser.addCommand("exit", function (parser, runtime, tokens) { if (tokens.matchToken("exit")) { return parseReturnFunction(parser, runtime, tokens, false); } }); parser.addCommand("halt", function (parser, runtime, tokens) { if (tokens.matchToken("halt")) { if (tokens.matchToken("the")) { tokens.requireToken("event"); // optional possessive if (tokens.matchOpToken("'")) { tokens.requireToken("s"); } var keepExecuting = true; } if (tokens.matchToken("bubbling")) { var bubbling = true; } else if (tokens.matchToken("default")) { var haltDefault = true; } var exit = parseReturnFunction(parser, runtime, tokens, false); var haltCmd = { keepExecuting: true, bubbling: bubbling, haltDefault: haltDefault, exit: exit, op: function (ctx) { if (ctx.event) { if (bubbling) { ctx.event.stopPropagation(); } else if (haltDefault) { ctx.event.preventDefault(); } else { ctx.event.stopPropagation(); ctx.event.preventDefault(); } if (keepExecuting) { return runtime.findNext(this, ctx); } else { return exit; } } }, }; return haltCmd; } }); parser.addCommand("log", function (parser, runtime, tokens) { if (!tokens.matchToken("log")) return; var exprs = [parser.parseElement("expression", tokens)]; while (tokens.matchOpToken(",")) { exprs.push(parser.requireElement("expression", tokens)); } if (tokens.matchToken("with")) { var withExpr = parser.requireElement("expression", tokens); } var logCmd = { exprs: exprs, withExpr: withExpr, args: [withExpr, exprs], op: function (ctx, withExpr, values) { if (withExpr) { withExpr.apply(null, values); } else { console.log.apply(null, values); } return runtime.findNext(this, ctx); }, }; return logCmd; }); parser.addCommand("beep!", function (parser, runtime, tokens) { if (!tokens.matchToken("beep!")) return; var exprs = [parser.parseElement("expression", tokens)]; while (tokens.matchOpToken(",")) { exprs.push(parser.requireElement("expression", tokens)); } var beepCmd = { exprs: exprs, args: [exprs], op: function (ctx, values) { for (let i = 0; i < exprs.length; i++) { const expr = exprs[i]; const val = values[i]; runtime.beepValueToConsole(ctx.me, expr, val); } return runtime.findNext(this, ctx); }, }; return beepCmd; }); parser.addCommand("throw", function (parser, runtime, tokens) { if (!tokens.matchToken("throw")) return; var expr = parser.requireElement("expression", tokens); var throwCmd = { expr: expr, args: [expr], op: function (ctx, expr) { runtime.registerHyperTrace(ctx, expr); throw expr; }, }; return throwCmd; }); var parseCallOrGet = function (parser, runtime, tokens) { var expr = parser.requireElement("expression", tokens); var callCmd = { expr: expr, args: [expr], op: function (context, result) { context.result = result; return runtime.findNext(callCmd, context); }, }; return callCmd; }; parser.addCommand("call", function (parser, runtime, tokens) { if (!tokens.matchToken("call")) return; var call = parseCallOrGet(parser, runtime, tokens); if (call.expr && call.expr.type !== "functionCall") { parser.raiseParseError(tokens, "Must be a function invocation"); } return call; }); parser.addCommand("get", function (parser, runtime, tokens) { if (tokens.matchToken("get")) { return parseCallOrGet(parser, runtime, tokens); } }); parser.addCommand("make", function (parser, runtime, tokens) { if (!tokens.matchToken("make")) return; tokens.matchToken("a") || tokens.matchToken("an"); var expr = parser.requireElement("expression", tokens); var args = []; if (expr.type !== "queryRef" && tokens.matchToken("from")) { do { args.push(parser.requireElement("expression", tokens)); } while (tokens.matchOpToken(",")); } if (tokens.matchToken("called")) { var target = parser.requireElement("symbol", tokens); } var command; if (expr.type === "queryRef") { command = { op: function (ctx) { var match, tagname = "div", id, classes = []; var re = /(?:(^|#|\.)([^#\. ]+))/g; while ((match = re.exec(expr.css))) { if (match[1] === "") tagname = match[2].trim(); else if (match[1] === "#") id = match[2].trim(); else classes.push(match[2].trim()); } var result = document.createElement(tagname); if (id !== undefined) result.id = id; for (var i = 0; i < classes.length; i++) { var cls = classes[i]; result.classList.add(cls) } ctx.result = result; if (target){ runtime.setSymbol(target.name, ctx, target.scope, result); } return runtime.findNext(this, ctx); }, }; return command; } else { command = { args: [expr, args], op: function (ctx, expr, args) { ctx.result = varargConstructor(expr, args); if (target){ runtime.setSymbol(target.name, ctx, target.scope, ctx.result); } return runtime.findNext(this, ctx); }, }; return command; } }); parser.addGrammarElement("pseudoCommand", function (parser, runtime, tokens) { let lookAhead = tokens.token(1); if (!(lookAhead && lookAhead.op && (lookAhead.value === '.' || lookAhead.value === "("))) { return null; } var expr = parser.requireElement("primaryExpression", tokens); var rootRoot = expr.root; var root = expr; while (rootRoot.root != null) { root = root.root; rootRoot = rootRoot.root; } if (expr.type !== "functionCall") { parser.raiseParseError(tokens, "Pseudo-commands must be function calls"); } if (root.type === "functionCall" && root.root.root == null) { if (tokens.matchAnyToken("the", "to", "on", "with", "into", "from", "at")) { var realRoot = parser.requireElement("expression", tokens); } else if (tokens.matchToken("me")) { var realRoot = parser.requireElement("implicitMeTarget", tokens); } } /** @type {ASTNode} */ var pseudoCommand if(realRoot){ pseudoCommand = { type: "pseudoCommand", root: realRoot, argExressions: root.argExressions, args: [realRoot, root.argExressions], op: function (context, rootRoot, args) { runtime.nullCheck(rootRoot, realRoot); var func = rootRoot[root.root.name]; runtime.nullCheck(func, root); if (func.hyperfunc) { args.push(context); } context.result = func.apply(rootRoot, args); return runtime.findNext(pseudoCommand, context); }, execute: function (context) { return runtime.unifiedExec(this, context); }, } } else { pseudoCommand = { type: "pseudoCommand", expr: expr, args: [expr], op: function (context, result) { context.result = result; return runtime.findNext(pseudoCommand, context); }, execute: function (context) { return runtime.unifiedExec(this, context); }, }; } return pseudoCommand; }); /** * @param {Parser} parser * @param {Runtime} runtime * @param {Tokens} tokens * @param {*} target * @param {*} value * @returns */ var makeSetter = function (parser, runtime, tokens, target, value) { var symbolWrite = target.type === "symbol"; var attributeWrite = target.type === "attributeRef"; var styleWrite = target.type === "styleRef"; var arrayWrite = target.type === "arrayIndex"; if (!(attributeWrite || styleWrite || symbolWrite) && target.root == null) { parser.raiseParseError(tokens, "Can only put directly into symbols, not references"); } var rootElt = null; var prop = null; if (symbolWrite) { // rootElt is null } else if (attributeWrite || styleWrite) { rootElt = parser.requireElement("implicitMeTarget", tokens); var attribute = target; } else if(arrayWrite) { prop = target.firstIndex; rootElt = target.root; } else { prop = target.prop ? target.prop.value : null; var attribute = target.attribute; rootElt = target.root; } /** @type {ASTNode} */ var setCmd = { target: target, symbolWrite: symbolWrite, value: value, args: [rootElt, prop, value], op: function (context, root, prop, valueToSet) { if (symbolWrite) { runtime.setSymbol(target.name, context, target.scope, valueToSet); } else { runtime.nullCheck(root, rootElt); if (arrayWrite) { root[prop] = valueToSet; } else { runtime.implicitLoop(root, function (elt) { if (attribute) { if (attribute.type === "attributeRef") { if (valueToSet == null) { elt.removeAttribute(attribute.name); } else { elt.setAttribute(attribute.name, valueToSet); } } else { elt.style[attribute.name] = valueToSet; } } else { elt[prop] = valueToSet; } }); } } return runtime.findNext(this, context); }, }; return setCmd; }; parser.addCommand("default", function (parser, runtime, tokens) { if (!tokens.matchToken("default")) return; var target = parser.requireElement("assignableExpression", tokens); tokens.requireToken("to"); var value = parser.requireElement("expression", tokens); /** @type {ASTNode} */ var setter = makeSetter(parser, runtime, tokens, target, value); var defaultCmd = { target: target, value: value, setter: setter, args: [target], op: function (context, target) { if (target) { return runtime.findNext(this, context); } else { return setter; } }, }; setter.parent = defaultCmd; return defaultCmd; }); parser.addCommand("set", function (parser, runtime, tokens) { if (!tokens.matchToken("set")) return; if (tokens.currentToken().type === "L_BRACE") { var obj = parser.requireElement("objectLiteral", tokens); tokens.requireToken("on"); var target = parser.requireElement("expression", tokens); var command = { objectLiteral: obj, target: target, args: [obj, target], op: function (ctx, obj, target) { Object.assign(target, obj); return runtime.findNext(this, ctx); }, }; return command; } try { tokens.pushFollow("to"); var target = parser.requireElement("assignableExpression", tokens); } finally { tokens.popFollow(); } tokens.requireToken("to"); var value = parser.requireElement("expression", tokens); return makeSetter(parser, runtime, tokens, target, value); }); parser.addCommand("if", function (parser, runtime, tokens) { if (!tokens.matchToken("if")) return; var expr = parser.requireElement("expression", tokens); tokens.matchToken("then"); // optional 'then' var trueBranch = parser.parseElement("commandList", tokens); var nestedIfStmt = false; if (tokens.matchToken("else") || tokens.matchToken("otherwise")) { nestedIfStmt = tokens.peekToken("if"); var falseBranch = parser.parseElement("commandList", tokens); } if (tokens.hasMore() && !nestedIfStmt) { tokens.requireToken("end"); } /** @type {ASTNode} */ var ifCmd = { expr: expr, trueBranch: trueBranch, falseBranch: falseBranch, args: [expr], op: function (context, exprValue) { if (exprValue) { return trueBranch; } else if (falseBranch) { return falseBranch; } else { return runtime.findNext(this, context); } }, }; parser.setParent(trueBranch, ifCmd); parser.setParent(falseBranch, ifCmd); return ifCmd; }); var parseRepeatExpression = function (parser, tokens, runtime, startedWithForToken) { var innerStartToken = tokens.currentToken(); var identifier; if (tokens.matchToken("for") || startedWithForToken) { var identifierToken = tokens.requireTokenType("IDENTIFIER"); identifier = identifierToken.value; tokens.requireToken("in"); var expression = parser.requireElement("expression", tokens); } else if (tokens.matchToken("in")) { identifier = "it"; var expression = parser.requireElement("expression", tokens); } else if (tokens.matchToken("while")) { var whileExpr = parser.requireElement("expression", tokens); } else if (tokens.matchToken("until")) { var isUntil = true; if (tokens.matchToken("event")) { var evt = parser.requireElement("dotOrColonPath", tokens, "Expected event name"); if (tokens.matchToken("from")) { var on = parser.requireElement("expression", tokens); } } else { var whileExpr = parser.requireElement("expression", tokens); } } else { if (!parser.commandBoundary(tokens.currentToken()) && tokens.currentToken().value !== 'forever') { var times = parser.requireElement("expression", tokens); tokens.requireToken("times"); } else { tokens.matchToken("forever"); // consume optional forever var forever = true; } } if (tokens.matchToken("index")) { var identifierToken = tokens.requireTokenType("IDENTIFIER"); var indexIdentifier = identifierToken.value; } var loop = parser.parseElement("commandList", tokens); if (loop && evt) { // if this is an event based loop, wait a tick at the end of the loop so that // events have a chance to trigger in the loop condition o_O))) var last = loop; while (last.next) { last = last.next; } var waitATick = { type: "waitATick", op: function () { return new Promise(function (resolve) { setTimeout(function () { resolve(runtime.findNext(waitATick)); }, 0); }); }, }; last.next = waitATick; } if (tokens.hasMore()) { tokens.requireToken("end"); } if (identifier == null) { identifier = "_implicit_repeat_" + innerStartToken.start; var slot = identifier; } else { var slot = identifier + "_" + innerStartToken.start; } var repeatCmd = { identifier: identifier, indexIdentifier: indexIdentifier, slot: slot, expression: expression, forever: forever, times: times, until: isUntil, event: evt, on: on, whileExpr: whileExpr, resolveNext: function () { return this; }, loop: loop, args: [whileExpr, times], op: function (context, whileValue, times) { var iteratorInfo = context.meta.iterators[slot]; var keepLooping = false; var loopVal = null; if (this.forever) { keepLooping = true; } else if (this.until) { if (evt) { keepLooping = context.meta.iterators[slot].eventFired === false; } else { keepLooping = whileValue !== true; } } else if (whileExpr) { keepLooping = whileValue; } else if (times) { keepLooping = iteratorInfo.index < times; } else { var nextValFromIterator = iteratorInfo.iterator.next(); keepLooping = !nextValFromIterator.done; loopVal = nextValFromIterator.value; } if (keepLooping) { if (iteratorInfo.value) { context.result = context.locals[identifier] = loopVal; } else { context.result = iteratorInfo.index; } if (indexIdentifier) { context.locals[indexIdentifier] = iteratorInfo.index; } iteratorInfo.index++; return loop; } else { context.meta.iterators[slot] = null; return runtime.findNext(this.parent, context); } }, }; parser.setParent(loop, repeatCmd); var repeatInit = { name: "repeatInit", args: [expression, evt, on], op: function (context, value, event, on) { var iteratorInfo = { index: 0, value: value, eventFired: false, }; context.meta.iterators[slot] = iteratorInfo; if (value && value[Symbol.iterator]) { iteratorInfo.iterator = value[Symbol.iterator](); } if (evt) { var target = on || context.me; target.addEventListener( event, function (e) { context.meta.iterators[slot].eventFired = true; }, { once: true } ); } return repeatCmd; // continue to loop }, execute: function (context) { return runtime.unifiedExec(this, context); }, }; parser.setParent(repeatCmd, repeatInit); return repeatInit; }; parser.addCommand("repeat", function (parser, runtime, tokens) { if (tokens.matchToken("repeat")) { return parseRepeatExpression(parser, tokens, runtime, false); } }); parser.addCommand("for", function (parser, runtime, tokens) { if (tokens.matchToken("for")) { return parseRepeatExpression(parser, tokens, runtime, true); } }); parser.addCommand("continue", function (parser, runtime, tokens) { if (!tokens.matchToken("continue")) return; var command = { op: function (context) { // scan for the closest repeat statement for (var parent = this.parent ; true ; parent = parent.parent) { if (parent == undefined) { parser.raiseParseError(tokens, "Command `continue` cannot be used outside of a `repeat` loop.") } if (parent.loop != undefined) { return parent.resolveNext(context) } } } }; return command; }); parser.addCommand("break", function (parser, runtime, tokens) { if (!tokens.matchToken("break")) return; var command = { op: function (context) { // scan for the closest repeat statement for (var parent = this.parent ; true ; parent = parent.parent) { if (parent == undefined) { parser.raiseParseError(tokens, "Command `continue` cannot be used outside of a `repeat` loop.") } if (parent.loop != undefined) { return runtime.findNext(parent.parent, context); } } } }; return command; }); parser.addGrammarElement("stringLike", function (parser, runtime, tokens) { return parser.parseAnyOf(["string", "nakedString"], tokens); }); parser.addCommand("append", function (parser, runtime, tokens) { if (!tokens.matchToken("append")) return; var targetExpr = null; var value = parser.requireElement("expression", tokens); /** @type {ASTNode} */ var implicitResultSymbol = { type: "symbol", evaluate: function (context) { return runtime.resolveSymbol("result", context); }, }; if (tokens.matchToken("to")) { targetExpr = parser.requireElement("expression", tokens); } else { targetExpr = implicitResultSymbol; } var setter = null; if (targetExpr.type === "symbol" || targetExpr.type === "attributeRef" || targetExpr.root != null) { setter = makeSetter(parser, runtime, tokens, targetExpr, implicitResultSymbol); } var command = { value: value, target: targetExpr, args: [targetExpr, value], op: function (context, target, value) { if (Array.isArray(target)) { target.push(value); return runtime.findNext(this, context); } else if (target instanceof Element) { target.innerHTML += value; return runtime.findNext(this, context); } else if(setter) { context.result = (target || "") + value; return setter; } else { throw Error("Unable to append a value!") } }, execute: function (context) { return runtime.unifiedExec(this, context/*, value, target*/); }, }; if (setter != null) { setter.parent = command; } return command; }); function parsePickRange(parser, runtime, tokens) { tokens.matchToken("at") || tokens.matchToken("from"); const rv = { includeStart: true, includeEnd: false } rv.from = tokens.matchToken("start") ? 0 : parser.requireElement("expression", tokens) if (tokens.matchToken("to") || tokens.matchOpToken("..")) { if (tokens.matchToken("end")) { rv.toEnd = true; } else { rv.to = parser.requireElement("expression", tokens); } } if (tokens.matchToken("inclusive")) rv.includeEnd = true; else if (tokens.matchToken("exclusive")) rv.includeStart = false; return rv; } class RegExpIterator { constructor(re, str) { this.re = re; this.str = str; } next() { const match = this.re.exec(this.str); if (match === null) return { done: true }; else return { value: match }; } } class RegExpIterable { constructor(re, flags, str) { this.re = re; this.flags = flags; this.str = str; } [Symbol.iterator]() { return new RegExpIterator(new RegExp(this.re, this.flags), this.str); } } parser.addCommand("pick", (parser, runtime, tokens) => { if (!tokens.matchToken("pick")) return; tokens.matchToken("the"); if (tokens.matchToken("item") || tokens.matchToken("items") || tokens.matchToken("character") || tokens.matchToken("characters")) { const range = parsePickRange(parser, runtime, tokens); tokens.requireToken("from"); const root = parser.requireElement("expression", tokens); return { args: [root, range.from, range.to], op(ctx, root, from, to) { if (range.toEnd) to = root.length; if (!range.includeStart) from++; if (range.includeEnd) to++; if (to == null || to == undefined) to = from + 1; ctx.result = root.slice(from, to); return runtime.findNext(this, ctx); } } } if (tokens.matchToken("match")) { tokens.matchToken("of"); const re = parser.parseElement("expression", tokens); let flags = "" if (tokens.matchOpToken("|")) { flags = tokens.requireToken("identifier").value; } tokens.requireToken("from"); const root = parser.parseElement("expression", tokens); return { args: [root, re], op(ctx, root, re) { ctx.result = new RegExp(re, flags).exec(root); return runtime.findNext(this, ctx); } } } if (tokens.matchToken("matches")) { tokens.matchToken("of"); const re = parser.parseElement("expression", tokens); let flags = "gu" if (tokens.matchOpToken("|")) { flags = 'g' + tokens.requireToken("identifier").value.replace('g', ''); } console.log('flags', flags) tokens.requireToken("from"); const root = parser.parseElement("expression", tokens); return { args: [root, re], op(ctx, root, re) { ctx.result = new RegExpIterable(re, flags, root); return runtime.findNext(this, ctx); } } } }); parser.addCommand("increment", function (parser, runtime, tokens) { if (!tokens.matchToken("increment")) return; var amountExpr; // This is optional. Defaults to "result" var target = parser.parseElement("assignableExpression", tokens); // This is optional. Defaults to 1. if (tokens.matchToken("by")) { amountExpr = parser.requireElement("expression", tokens); } var implicitIncrementOp = { type: "implicitIncrementOp", target: target, args: [target, amountExpr], op: function (context, targetValue, amount) { targetValue = targetValue ? parseFloat(targetValue) : 0; amount = amountExpr ? parseFloat(amount) : 1; var newValue = targetValue + amount; context.result = newValue; return newValue; }, evaluate: function (context) { return runtime.unifiedEval(this, context); } }; return makeSetter(parser, runtime, tokens, target, implicitIncrementOp); }); parser.addCommand("decrement", function (parser, runtime, tokens) { if (!tokens.matchToken("decrement")) return; var amountExpr; // This is optional. Defaults to "result" var target = parser.parseElement("assignableExpression", tokens); // This is optional. Defaults to 1. if (tokens.matchToken("by")) { amountExpr = parser.requireElement("expression", tokens); } var implicitDecrementOp = { type: "implicitDecrementOp", target: target, args: [target, amountExpr], op: function (context, targetValue, amount) { targetValue = targetValue ? parseFloat(targetValue) : 0; amount = amountExpr ? parseFloat(amount) : 1; var newValue = targetValue - amount; context.result = newValue; return newValue; }, evaluate: function (context) { return runtime.unifiedEval(this, context); } }; return makeSetter(parser, runtime, tokens, target, implicitDecrementOp); }); function parseConversionInfo(tokens, parser) { var type = "text"; var conversion; tokens.matchToken("a") || tokens.matchToken("an"); if (tokens.matchToken("json") || tokens.matchToken("Object")) { type = "json"; } else if (tokens.matchToken("response")) { type = "response"; } else if (tokens.matchToken("html")) { type = "html"; } else if (tokens.matchToken("text")) { // default, ignore } else { conversion = parser.requireElement("dotOrColonPath", tokens).evaluate(); } return {type, conversion}; } parser.addCommand("fetch", function (parser, runtime, tokens) { if (!tokens.matchToken("fetch")) return; var url = parser.requireElement("stringLike", tokens); if (tokens.matchToken("as")) { var conversionInfo = parseConversionInfo(tokens, parser); } if (tokens.matchToken("with") && tokens.currentToken().value !== "{") { var args = parser.parseElement("nakedNamedArgumentList", tokens); } else { var args = parser.parseElement("objectLiteral", tokens); } if (conversionInfo == null && tokens.matchToken("as")) { conversionInfo = parseConversionInfo(tokens, parser); } var type = conversionInfo ? conversionInfo.type : "text"; var conversion = conversionInfo ? conversionInfo.conversion : null /** @type {ASTNode} */ var fetchCmd = { url: url, argExpressions: args, args: [url, args], op: function (context, url, args) { var detail = args || {}; detail["sender"] = context.me; detail["headers"] = detail["headers"] || {} var abortController = new AbortController(); let abortListener = context.me.addEventListener('fetch:abort', function(){ abortController.abort(); }, {once: true}); detail['signal'] = abortController.signal; runtime.triggerEvent(context.me, "hyperscript:beforeFetch", detail); runtime.triggerEvent(context.me, "fetch:beforeRequest", detail); args = detail; var finished = false; if (args.timeout) { setTimeout(function () { if (!finished) { abortController.abort(); } }, args.timeout); } return fetch(url, args) .then(function (resp) { let resultDetails = {response:resp}; runtime.triggerEvent(context.me, "fetch:afterResponse", resultDetails); resp = resultDetails.response; if (type === "response") { context.result = resp; runtime.triggerEvent(context.me, "fetch:afterRequest", {result:resp}); finished = true; return runtime.findNext(fetchCmd, context); } if (type === "json") { return resp.json().then(function (result) { context.result = result; runtime.triggerEvent(context.me, "fetch:afterRequest", {result}); finished = true; return runtime.findNext(fetchCmd, context); }); } return resp.text().then(function (result) { if (conversion) result = runtime.convertValue(result, conversion); if (type === "html") result = runtime.convertValue(result, "Fragment"); context.result = result; runtime.triggerEvent(context.me, "fetch:afterRequest", {result}); finished = true; return runtime.findNext(fetchCmd, context); }); }) .catch(function (reason) { runtime.triggerEvent(context.me, "fetch:error", { reason: reason, }); throw reason; }).finally(function(){ context.me.removeEventListener('fetch:abort', abortListener); }); }, }; return fetchCmd; }); } function hyperscriptWebGrammar(parser) { parser.addCommand("settle", function (parser, runtime, tokens) { if (tokens.matchToken("settle")) { if (!parser.commandBoundary(tokens.currentToken())) { var onExpr = parser.requireElement("expression", tokens); } else { var onExpr = parser.requireElement("implicitMeTarget", tokens); } var settleCommand = { type: "settleCmd", args: [onExpr], op: function (context, on) { runtime.nullCheck(on, onExpr); var resolve = null; var resolved = false; var transitionStarted = false; var promise = new Promise(function (r) { resolve = r; }); // listen for a transition begin on.addEventListener( "transitionstart", function () { transitionStarted = true; }, { once: true } ); // if no transition begins in 500ms, cancel setTimeout(function () { if (!transitionStarted && !resolved) { resolve(runtime.findNext(settleCommand, context)); } }, 500); // continue on a transition emd on.addEventListener( "transitionend", function () { if (!resolved) { resolve(runtime.findNext(settleCommand, context)); } }, { once: true } ); return promise; }, execute: function (context) { return runtime.unifiedExec(this, context); }, }; return settleCommand; } }); parser.addCommand("add", function (parser, runtime, tokens) { if (tokens.matchToken("add")) { var classRef = parser.parseElement("classRef", tokens); var attributeRef = null; var cssDeclaration = null; if (classRef == null) { attributeRef = parser.parseElement("attributeRef", tokens); if (attributeRef == null) { cssDeclaration = parser.parseElement("styleLiteral", tokens); if (cssDeclaration == null) { parser.raiseParseError(tokens, "Expected either a class reference or attribute expression"); } } } else { var classRefs = [classRef]; while ((classRef = parser.parseElement("classRef", tokens))) { classRefs.push(classRef); } } if (tokens.matchToken("to")) { var toExpr = parser.requireElement("expression", tokens); } else { var toExpr = parser.requireElement("implicitMeTarget", tokens); } if (tokens.matchToken("when")) { if (cssDeclaration) { parser.raiseParseError(tokens, "Only class and properties are supported with a when clause") } var when = parser.requireElement("expression", tokens); } if (classRefs) { return { classRefs: classRefs, to: toExpr, args: [toExpr, classRefs], op: function (context, to, classRefs) { runtime.nullCheck(to, toExpr); runtime.forEach(classRefs, function (classRef) { runtime.implicitLoop(to, function (target) { if (when) { context.result = target; let whenResult = runtime.evaluateNoPromise(when, context); if (whenResult) { if (target instanceof Element) target.classList.add(classRef.className); } else { if (target instanceof Element) target.classList.remove(classRef.className); } context.result = null; } else { if (target instanceof Element) target.classList.add(classRef.className); } }); }); return runtime.findNext(this, context); }, }; } else if (attributeRef) { return { type: "addCmd", attributeRef: attributeRef, to: toExpr, args: [toExpr], op: function (context, to, attrRef) { runtime.nullCheck(to, toExpr); runtime.implicitLoop(to, function (target) { if (when) { context.result = target; let whenResult = runtime.evaluateNoPromise(when, context); if (whenResult) { target.setAttribute(attributeRef.name, attributeRef.value); } else { target.removeAttribute(attributeRef.name); } context.result = null; } else { target.setAttribute(attributeRef.name, attributeRef.value); } }); return runtime.findNext(this, context); }, execute: function (ctx) { return runtime.unifiedExec(this, ctx); }, }; } else { return { type: "addCmd", cssDeclaration: cssDeclaration, to: toExpr, args: [toExpr, cssDeclaration], op: function (context, to, css) { runtime.nullCheck(to, toExpr); runtime.implicitLoop(to, function (target) { target.style.cssText += css; }); return runtime.findNext(this, context); }, execute: function (ctx) { return runtime.unifiedExec(this, ctx); }, }; } } }); parser.addGrammarElement("styleLiteral", function (parser, runtime, tokens) { if (!tokens.matchOpToken("{")) return; var stringParts = [""] var exprs = [] while (tokens.hasMore()) { if (tokens.matchOpToken("\\")) { tokens.consumeToken(); } else if (tokens.matchOpToken("}")) { break; } else if (tokens.matchToken("$")) { var opencurly = tokens.matchOpToken("{"); var expr = parser.parseElement("expression", tokens); if (opencurly) tokens.requireOpToken("}"); exprs.push(expr) stringParts.push("") } else { var tok = tokens.consumeToken(); stringParts[stringParts.length-1] += tokens.source.substring(tok.start, tok.end); } stringParts[stringParts.length-1] += tokens.lastWhitespace(); } return { type: "styleLiteral", args: [exprs], op: function (ctx, exprs) { var rv = ""; stringParts.forEach(function (part, idx) { rv += part; if (idx in exprs) rv += exprs[idx]; }); return rv; }, evaluate: function(ctx) { return runtime.unifiedEval(this, ctx); } } }) parser.addCommand("remove", function (parser, runtime, tokens) { if (tokens.matchToken("remove")) { var classRef = parser.parseElement("classRef", tokens); var attributeRef = null; var elementExpr = null; if (classRef == null) { attributeRef = parser.parseElement("attributeRef", tokens); if (attributeRef == null) { elementExpr = parser.parseElement("expression", tokens); if (elementExpr == null) { parser.raiseParseError( tokens, "Expected either a class reference, attribute expression or value expression" ); } } } else { var classRefs = [classRef]; while ((classRef = parser.parseElement("classRef", tokens))) { classRefs.push(classRef); } } if (tokens.matchToken("from")) { var fromExpr = parser.requireElement("expression", tokens); } else { if (elementExpr == null) { var fromExpr = parser.requireElement("implicitMeTarget", tokens); } } if (elementExpr) { return { elementExpr: elementExpr, from: fromExpr, args: [elementExpr, fromExpr], op: function (context, element, from) { runtime.nullCheck(element, elementExpr); runtime.implicitLoop(element, function (target) { if (target.parentElement && (from == null || from.contains(target))) { target.parentElement.removeChild(target); } }); return runtime.findNext(this, context); }, }; } else { return { classRefs: classRefs, attributeRef: attributeRef, elementExpr: elementExpr, from: fromExpr, args: [classRefs, fromExpr], op: function (context, classRefs, from) { runtime.nullCheck(from, fromExpr); if (classRefs) { runtime.forEach(classRefs, function (classRef) { runtime.implicitLoop(from, function (target) { target.classList.remove(classRef.className); }); }); } else { runtime.implicitLoop(from, function (target) { target.removeAttribute(attributeRef.name); }); } return runtime.findNext(this, context); }, }; } } }); parser.addCommand("toggle", function (parser, runtime, tokens) { if (tokens.matchToken("toggle")) { tokens.matchAnyToken("the", "my"); if (tokens.currentToken().type === "STYLE_REF") { let styleRef = tokens.consumeToken(); var name = styleRef.value.substr(1); var visibility = true; var hideShowStrategy = resolveHideShowStrategy(parser, tokens, name); if (tokens.matchToken("of")) { tokens.pushFollow("with"); try { var onExpr = parser.requireElement("expression", tokens); } finally { tokens.popFollow(); } } else { var onExpr = parser.requireElement("implicitMeTarget", tokens); } } else if (tokens.matchToken("between")) { var between = true; var classRef = parser.parseElement("classRef", tokens); tokens.requireToken("and"); var classRef2 = parser.requireElement("classRef", tokens); } else { var classRef = parser.parseElement("classRef", tokens); var attributeRef = null; if (classRef == null) { attributeRef = parser.parseElement("attributeRef", tokens); if (attributeRef == null) { parser.raiseParseError(tokens, "Expected either a class reference or attribute expression"); } } else { var classRefs = [classRef]; while ((classRef = parser.parseElement("classRef", tokens))) { classRefs.push(classRef); } } } if (visibility !== true) { if (tokens.matchToken("on")) { var onExpr = parser.requireElement("expression", tokens); } else { var onExpr = parser.requireElement("implicitMeTarget", tokens); } } if (tokens.matchToken("for")) { var time = parser.requireElement("expression", tokens); } else if (tokens.matchToken("until")) { var evt = parser.requireElement("dotOrColonPath", tokens, "Expected event name"); if (tokens.matchToken("from")) { var from = parser.requireElement("expression", tokens); } } var toggleCmd = { classRef: classRef, classRef2: classRef2, classRefs: classRefs, attributeRef: attributeRef, on: onExpr, time: time, evt: evt, from: from, toggle: function (on, classRef, classRef2, classRefs) { runtime.nullCheck(on, onExpr); if (visibility) { runtime.implicitLoop(on, function (target) { hideShowStrategy("toggle", target); }); } else if (between) { runtime.implicitLoop(on, function (target) { if (target.classList.contains(classRef.className)) { target.classList.remove(classRef.className); target.classList.add(classRef2.className); } else { target.classList.add(classRef.className); target.classList.remove(classRef2.className); } }); } else if (classRefs) { runtime.forEach(classRefs, function (classRef) { runtime.implicitLoop(on, function (target) { target.classList.toggle(classRef.className); }); }); } else { runtime.forEach(on, function (target) { if (target.hasAttribute(attributeRef.name)) { target.removeAttribute(attributeRef.name); } else { target.setAttribute(attributeRef.name, attributeRef.value); } }); } }, args: [onExpr, time, evt, from, classRef, classRef2, classRefs], op: function (context, on, time, evt, from, classRef, classRef2, classRefs) { if (time) { return new Promise(function (resolve) { toggleCmd.toggle(on, classRef, classRef2, classRefs); setTimeout(function () { toggleCmd.toggle(on, classRef, classRef2, classRefs); resolve(runtime.findNext(toggleCmd, context)); }, time); }); } else if (evt) { return new Promise(function (resolve) { var target = from || context.me; target.addEventListener( evt, function () { toggleCmd.toggle(on, classRef, classRef2, classRefs); resolve(runtime.findNext(toggleCmd, context)); }, { once: true } ); toggleCmd.toggle(on, classRef, classRef2, classRefs); }); } else { this.toggle(on, classRef, classRef2, classRefs); return runtime.findNext(toggleCmd, context); } }, }; return toggleCmd; } }); var HIDE_SHOW_STRATEGIES = { display: function (op, element, arg) { if (arg) { element.style.display = arg; } else if (op === "toggle") { if (getComputedStyle(element).display === "none") { HIDE_SHOW_STRATEGIES.display("show", element, arg); } else { HIDE_SHOW_STRATEGIES.display("hide", element, arg); } } else if (op === "hide") { const internalData = parser.runtime.getInternalData(element); if (internalData.originalDisplay == null) { internalData.originalDisplay = element.style.display; } element.style.display = "none"; } else { const internalData = parser.runtime.getInternalData(element); if (internalData.originalDisplay && internalData.originalDisplay !== 'none') { element.style.display = internalData.originalDisplay; } else { element.style.removeProperty('display'); } } }, visibility: function (op, element, arg) { if (arg) { element.style.visibility = arg; } else if (op === "toggle") { if (getComputedStyle(element).visibility === "hidden") { HIDE_SHOW_STRATEGIES.visibility("show", element, arg); } else { HIDE_SHOW_STRATEGIES.visibility("hide", element, arg); } } else if (op === "hide") { element.style.visibility = "hidden"; } else { element.style.visibility = "visible"; } }, opacity: function (op, element, arg) { if (arg) { element.style.opacity = arg; } else if (op === "toggle") { if (getComputedStyle(element).opacity === "0") { HIDE_SHOW_STRATEGIES.opacity("show", element, arg); } else { HIDE_SHOW_STRATEGIES.opacity("hide", element, arg); } } else if (op === "hide") { element.style.opacity = "0"; } else { element.style.opacity = "1"; } }, }; var parseShowHideTarget = function (parser, runtime, tokens) { var target; var currentTokenValue = tokens.currentToken(); if (currentTokenValue.value === "when" || currentTokenValue.value === "with" || parser.commandBoundary(currentTokenValue)) { target = parser.parseElement("implicitMeTarget", tokens); } else { target = parser.parseElement("expression", tokens); } return target; }; var resolveHideShowStrategy = function (parser, tokens, name) { var configDefault = config.defaultHideShowStrategy; var strategies = HIDE_SHOW_STRATEGIES; if (config.hideShowStrategies) { strategies = Object.assign(strategies, config.hideShowStrategies); // merge in user provided strategies } name = name || configDefault || "display"; var value = strategies[name]; if (value == null) { parser.raiseParseError(tokens, "Unknown show/hide strategy : " + name); } return value; }; parser.addCommand("hide", function (parser, runtime, tokens) { if (tokens.matchToken("hide")) { var targetExpr = parseShowHideTarget(parser, runtime, tokens); var name = null; if (tokens.matchToken("with")) { name = tokens.requireTokenType("IDENTIFIER", "STYLE_REF").value; if (name.indexOf("*") === 0) { name = name.substr(1); } } var hideShowStrategy = resolveHideShowStrategy(parser, tokens, name); return { target: targetExpr, args: [targetExpr], op: function (ctx, target) { runtime.nullCheck(target, targetExpr); runtime.implicitLoop(target, function (elt) { hideShowStrategy("hide", elt); }); return runtime.findNext(this, ctx); }, }; } }); parser.addCommand("show", function (parser, runtime, tokens) { if (tokens.matchToken("show")) { var targetExpr = parseShowHideTarget(parser, runtime, tokens); var name = null; if (tokens.matchToken("with")) { name = tokens.requireTokenType("IDENTIFIER", "STYLE_REF").value; if (name.indexOf("*") === 0) { name = name.substr(1); } } var arg = null; if (tokens.matchOpToken(":")) { var tokenArr = tokens.consumeUntilWhitespace(); tokens.matchTokenType("WHITESPACE"); arg = tokenArr .map(function (t) { return t.value; }) .join(""); } if (tokens.matchToken("when")) { var when = parser.requireElement("expression", tokens); } var hideShowStrategy = resolveHideShowStrategy(parser, tokens, name); return { target: targetExpr, when: when, args: [targetExpr], op: function (ctx, target) { runtime.nullCheck(target, targetExpr); runtime.implicitLoop(target, function (elt) { if (when) { ctx.result = elt; let whenResult = runtime.evaluateNoPromise(when, ctx); if (whenResult) { hideShowStrategy("show", elt, arg); } else { hideShowStrategy("hide", elt); } ctx.result = null; } else { hideShowStrategy("show", elt, arg); } }); return runtime.findNext(this, ctx); }, }; } }); parser.addCommand("take", function (parser, runtime, tokens) { if (tokens.matchToken("take")) { var classRef = parser.parseElement("classRef", tokens); var attributeRef = null; var replacementValue = null; if (classRef == null) { attributeRef = parser.parseElement("attributeRef", tokens); if (attributeRef == null) { parser.raiseParseError(tokens, "Expected either a class reference or attribute expression"); } if (tokens.matchToken("with")) { replacementValue = parser.requireElement("expression", tokens); } } if (tokens.matchToken("from")) { var fromExpr = parser.requireElement("expression", tokens); } else { var fromExpr = classRef; } if (tokens.matchToken("for")) { var forExpr = parser.requireElement("expression", tokens); } else { var forExpr = parser.requireElement("implicitMeTarget", tokens); } if (classRef) { var takeCmd = { classRef: classRef, from: fromExpr, forElt: forExpr, args: [classRef, fromExpr, forExpr], op: function (context, eltColl, from, forElt) { runtime.nullCheck(from, fromExpr); runtime.nullCheck(forElt, forExpr); var clazz = eltColl.className; runtime.implicitLoop(from, function (target) { target.classList.remove(clazz); }); runtime.implicitLoop(forElt, function (target) { target.classList.add(clazz); }); return runtime.findNext(this, context); }, }; return takeCmd; } else { var takeCmd = { attributeRef: attributeRef, from: fromExpr, forElt: forExpr, args: [fromExpr, forExpr, replacementValue], op: function (context, from, forElt, replacementValue) { runtime.nullCheck(from, fromExpr); runtime.nullCheck(forElt, forExpr); runtime.implicitLoop(from, function (target) { if (!replacementValue) { target.removeAttribute(attributeRef.name); } else { target.setAttribute(attributeRef.name, replacementValue) } }); runtime.implicitLoop(forElt, function (target) { target.setAttribute(attributeRef.name, attributeRef.value || "") }); return runtime.findNext(this, context); }, }; return takeCmd; } } }); function putInto(runtime, context, prop, valueToPut) { if (prop != null) { var value = runtime.resolveSymbol(prop, context); } else { var value = context; } if (value instanceof Element || value instanceof HTMLDocument) { while (value.firstChild) value.removeChild(value.firstChild); value.append(parser.runtime.convertValue(valueToPut, "Fragment")); runtime.processNode(value); } else { if (prop != null) { runtime.setSymbol(prop, context, null, valueToPut); } else { throw "Don't know how to put a value into " + typeof context; } } } parser.addCommand("put", function (parser, runtime, tokens) { if (tokens.matchToken("put")) { var value = parser.requireElement("expression", tokens); var operationToken = tokens.matchAnyToken("into", "before", "after"); if (operationToken == null && tokens.matchToken("at")) { tokens.matchToken("the"); // optional "the" operationToken = tokens.matchAnyToken("start", "end"); tokens.requireToken("of"); } if (operationToken == null) { parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'"); } var target = parser.requireElement("expression", tokens); var operation = operationToken.value; var arrayIndex = false; var symbolWrite = false; var rootExpr = null; var prop = null; if (target.type === "arrayIndex" && operation === "into") { arrayIndex = true; prop = target.prop; rootExpr = target.root; } else if (target.prop && target.root && operation === "into") { prop = target.prop.value; rootExpr = target.root; } else if (target.type === "symbol" && operation === "into") { symbolWrite = true; prop = target.name; } else if (target.type === "attributeRef" && operation === "into") { var attributeWrite = true; prop = target.name; rootExpr = parser.requireElement("implicitMeTarget", tokens); } else if (target.type === "styleRef" && operation === "into") { var styleWrite = true; prop = target.name; rootExpr = parser.requireElement("implicitMeTarget", tokens); } else if (target.attribute && operation === "into") { var attributeWrite = target.attribute.type === "attributeRef"; var styleWrite = target.attribute.type === "styleRef"; prop = target.attribute.name; rootExpr = target.root; } else { rootExpr = target; } var putCmd = { target: target, operation: operation, symbolWrite: symbolWrite, value: value, args: [rootExpr, prop, value], op: function (context, root, prop, valueToPut) { if (symbolWrite) { putInto(runtime, context, prop, valueToPut); } else { runtime.nullCheck(root, rootExpr); if (operation === "into") { if (attributeWrite) { runtime.implicitLoop(root, function (elt) { elt.setAttribute(prop, valueToPut); }); } else if (styleWrite) { runtime.implicitLoop(root, function (elt) { elt.style[prop] = valueToPut; }); } else if (arrayIndex) { root[prop] = valueToPut; } else { runtime.implicitLoop(root, function (elt) { putInto(runtime, elt, prop, valueToPut); }); } } else { var op = operation === "before" ? Element.prototype.before : operation === "after" ? Element.prototype.after : operation === "start" ? Element.prototype.prepend : operation === "end" ? Element.prototype.append : Element.prototype.append; // unreachable runtime.implicitLoop(root, function (elt) { op.call( elt, valueToPut instanceof Node ? valueToPut : runtime.convertValue(valueToPut, "Fragment") ); // process any new content if (elt.parentElement) { runtime.processNode(elt.parentElement); } else { runtime.processNode(elt); } }); } } return runtime.findNext(this, context); }, }; return putCmd; } }); function parsePseudopossessiveTarget(parser, runtime, tokens) { var targets; if ( tokens.matchToken("the") || tokens.matchToken("element") || tokens.matchToken("elements") || tokens.currentToken().type === "CLASS_REF" || tokens.currentToken().type === "ID_REF" || (tokens.currentToken().op && tokens.currentToken().value === "<") ) { parser.possessivesDisabled = true; try { targets = parser.parseElement("expression", tokens); } finally { delete parser.possessivesDisabled; } // optional possessive if (tokens.matchOpToken("'")) { tokens.requireToken("s"); } } else if (tokens.currentToken().type === "IDENTIFIER" && tokens.currentToken().value === "its") { var identifier = tokens.matchToken("its"); targets = { type: "pseudopossessiveIts", token: identifier, name: identifier.value, evaluate: function (context) { return runtime.resolveSymbol("it", context); }, }; } else { tokens.matchToken("my") || tokens.matchToken("me"); // consume optional 'my' targets = parser.parseElement("implicitMeTarget", tokens); } return targets; } parser.addCommand("transition", function (parser, runtime, tokens) { if (tokens.matchToken("transition")) { var targetsExpr = parsePseudopossessiveTarget(parser, runtime, tokens); var properties = []; var from = []; var to = []; var currentToken = tokens.currentToken(); while ( !parser.commandBoundary(currentToken) && currentToken.value !== "over" && currentToken.value !== "using" ) { if (tokens.currentToken().type === "STYLE_REF") { let styleRef = tokens.consumeToken(); let styleProp = styleRef.value.substr(1); properties.push({ type: "styleRefValue", evaluate: function () { return styleProp; }, }); } else { properties.push(parser.requireElement("stringLike", tokens)); } if (tokens.matchToken("from")) { from.push(parser.requireElement("expression", tokens)); } else { from.push(null); } tokens.requireToken("to"); if (tokens.matchToken("initial")) { to.push({ type: "initial_literal", evaluate : function(){ return "initial"; } }); } else { to.push(parser.requireElement("expression", tokens)); } currentToken = tokens.currentToken(); } if (tokens.matchToken("over")) { var over = parser.requireElement("expression", tokens); } else if (tokens.matchToken("using")) { var using = parser.requireElement("expression", tokens); } var transition = { to: to, args: [targetsExpr, properties, from, to, using, over], op: function (context, targets, properties, from, to, using, over) { runtime.nullCheck(targets, targetsExpr); var promises = []; runtime.implicitLoop(targets, function (target) { var promise = new Promise(function (resolve, reject) { var initialTransition = target.style.transition; if (over) { target.style.transition = "all " + over + "ms ease-in"; } else if (using) { target.style.transition = using; } else { target.style.transition = config.defaultTransition; } var internalData = runtime.getInternalData(target); var computedStyles = getComputedStyle(target); var initialStyles = {}; for (var i = 0; i < computedStyles.length; i++) { var name = computedStyles[i]; var initialValue = computedStyles[name]; initialStyles[name] = initialValue; } // store intitial values if (!internalData.initalStyles) { internalData.initalStyles = initialStyles; } for (var i = 0; i < properties.length; i++) { var property = properties[i]; var fromVal = from[i]; if (fromVal === "computed" || fromVal == null) { target.style[property] = initialStyles[property]; } else { target.style[property] = fromVal; } } //console.log("transition started", transition); var transitionStarted = false; var resolved = false; target.addEventListener( "transitionend", function () { if (!resolved) { //console.log("transition ended", transition); target.style.transition = initialTransition; resolved = true; resolve(); } }, { once: true } ); target.addEventListener( "transitionstart", function () { transitionStarted = true; }, { once: true } ); // it no transition has started in 100ms, continue setTimeout(function () { if (!resolved && !transitionStarted) { //console.log("transition ended", transition); target.style.transition = initialTransition; resolved = true; resolve(); } }, 100); setTimeout(function () { var autoProps = []; for (var i = 0; i < properties.length; i++) { var property = properties[i]; var toVal = to[i]; if (toVal === "initial") { var propertyValue = internalData.initalStyles[property]; target.style[property] = propertyValue; } else { target.style[property] = toVal; } //console.log("set", property, "to", target.style[property], "on", target, "value passed in : ", toVal); } }, 0); }); promises.push(promise); }); return Promise.all(promises).then(function () { return runtime.findNext(transition, context); }); }, }; return transition; } }); parser.addCommand("measure", function (parser, runtime, tokens) { if (!tokens.matchToken("measure")) return; var targetExpr = parsePseudopossessiveTarget(parser, runtime, tokens); var propsToMeasure = []; if (!parser.commandBoundary(tokens.currentToken())) do { propsToMeasure.push(tokens.matchTokenType("IDENTIFIER").value); } while (tokens.matchOpToken(",")); return { properties: propsToMeasure, args: [targetExpr], op: function (ctx, target) { runtime.nullCheck(target, targetExpr); if (0 in target) target = target[0]; // not measuring multiple elts var rect = target.getBoundingClientRect(); var scroll = { top: target.scrollTop, left: target.scrollLeft, topMax: target.scrollTopMax, leftMax: target.scrollLeftMax, height: target.scrollHeight, width: target.scrollWidth, }; ctx.result = { x: rect.x, y: rect.y, left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom, width: rect.width, height: rect.height, bounds: rect, scrollLeft: scroll.left, scrollTop: scroll.top, scrollLeftMax: scroll.leftMax, scrollTopMax: scroll.topMax, scrollWidth: scroll.width, scrollHeight: scroll.height, scroll: scroll, }; runtime.forEach(propsToMeasure, function (prop) { if (prop in ctx.result) ctx.locals[prop] = ctx.result[prop]; else throw "No such measurement as " + prop; }); return runtime.findNext(this, ctx); }, }; }); parser.addLeafExpression("closestExpr", function (parser, runtime, tokens) { if (tokens.matchToken("closest")) { if (tokens.matchToken("parent")) { var parentSearch = true; } var css = null; if (tokens.currentToken().type === "ATTRIBUTE_REF") { var attributeRef = parser.requireElement("attributeRefAccess", tokens, null); css = "[" + attributeRef.attribute.name + "]"; } if (css == null) { var expr = parser.requireElement("expression", tokens); if (expr.css == null) { parser.raiseParseError(tokens, "Expected a CSS expression"); } else { css = expr.css; } } if (tokens.matchToken("to")) { var to = parser.parseElement("expression", tokens); } else { var to = parser.parseElement("implicitMeTarget", tokens); } var closestExpr = { type: "closestExpr", parentSearch: parentSearch, expr: expr, css: css, to: to, args: [to], op: function (ctx, to) { if (to == null) { return null; } else { let result = []; runtime.implicitLoop(to, function(to){ if (parentSearch) { result.push(to.parentElement ? to.parentElement.closest(css) : null); } else { result.push(to.closest(css)); } }) if (runtime.shouldAutoIterate(to)) { return result; } else { return result[0]; } } }, evaluate: function (context) { return runtime.unifiedEval(this, context); }, }; if (attributeRef) { attributeRef.root = closestExpr; attributeRef.args = [closestExpr]; return attributeRef; } else { return closestExpr; } } }); parser.addCommand("go", function (parser, runtime, tokens) { if (tokens.matchToken("go")) { if (tokens.matchToken("back")) { var back = true; } else { tokens.matchToken("to"); if (tokens.matchToken("url")) { var target = parser.requireElement("stringLike", tokens); var url = true; if (tokens.matchToken("in")) { tokens.requireToken("new"); tokens.requireToken("window"); var newWindow = true; } } else { tokens.matchToken("the"); // optional the var verticalPosition = tokens.matchAnyToken("top", "middle", "bottom"); var horizontalPosition = tokens.matchAnyToken("left", "center", "right"); if (verticalPosition || horizontalPosition) { tokens.requireToken("of"); } var target = parser.requireElement("unaryExpression", tokens); var plusOrMinus = tokens.matchAnyOpToken("+", "-"); if (plusOrMinus) { tokens.pushFollow("px"); try { var offset = parser.requireElement("expression", tokens); } finally { tokens.popFollow(); } } tokens.matchToken("px"); // optional px var smoothness = tokens.matchAnyToken("smoothly", "instantly"); var scrollOptions = {}; if (verticalPosition) { if (verticalPosition.value === "top") { scrollOptions.block = "start"; } else if (verticalPosition.value === "bottom") { scrollOptions.block = "end"; } else if (verticalPosition.value === "middle") { scrollOptions.block = "center"; } } if (horizontalPosition) { if (horizontalPosition.value === "left") { scrollOptions.inline = "start"; } else if (horizontalPosition.value === "center") { scrollOptions.inline = "center"; } else if (horizontalPosition.value === "right") { scrollOptions.inline = "end"; } } if (smoothness) { if (smoothness.value === "smoothly") { scrollOptions.behavior = "smooth"; } else if (smoothness.value === "instantly") { scrollOptions.behavior = "instant"; } } } } var goCmd = { target: target, args: [target, offset], op: function (ctx, to, offset) { if (back) { window.history.back(); } else if (url) { if (to) { if (newWindow) { window.open(to); } else { window.location.href = to; } } } else { runtime.implicitLoop(to, function (target) { if (target === window) { target = document.body; } if(plusOrMinus) { // a top scroll w/ an offset of some sort var boundingRect = target.getBoundingClientRect(); let scrollShim = document.createElement('div'); if (plusOrMinus.value === "-") { var finalOffset = -offset; } else { var finalOffset = - -offset; } scrollShim.style.position = 'absolute'; scrollShim.style.top = (boundingRect.x + finalOffset) + "px"; scrollShim.style.left = (boundingRect.y + finalOffset) + "px"; scrollShim.style.height = (boundingRect.height + (2 * finalOffset)) + "px"; scrollShim.style.width = (boundingRect.width + (2 * finalOffset)) + "px"; scrollShim.style.zIndex = "" + Number.MIN_SAFE_INTEGER; scrollShim.style.opacity = "0"; document.body.appendChild(scrollShim); setTimeout(function () { document.body.removeChild(scrollShim); }, 100); target = scrollShim; } target.scrollIntoView(scrollOptions); }); } return runtime.findNext(goCmd, ctx); }, }; return goCmd; } }); config.conversions.dynamicResolvers.push(function (str, node) { if (!(str === "Values" || str.indexOf("Values:") === 0)) { return; } var conversion = str.split(":")[1]; /** @type Object */ var result = {}; var implicitLoop = parser.runtime.implicitLoop.bind(parser.runtime); implicitLoop(node, function (/** @type HTMLInputElement */ node) { // Try to get a value directly from this node var input = getInputInfo(node); if (input !== undefined) { result[input.name] = input.value; return; } // Otherwise, try to query all child elements of this node that *should* contain values. if (node.querySelectorAll != undefined) { /** @type {NodeListOf} */ var children = node.querySelectorAll("input,select,textarea"); children.forEach(appendValue); } }); if (conversion) { if (conversion === "JSON") { return JSON.stringify(result); } else if (conversion === "Form") { /** @ts-ignore */ // TODO: does this work with multiple inputs of the same name? return new URLSearchParams(result).toString(); } else { throw "Unknown conversion: " + conversion; } } else { return result; } /** * @param {HTMLInputElement} node */ function appendValue(node) { var info = getInputInfo(node); if (info == undefined) { return; } // If there is no value already stored in this space. if (result[info.name] == undefined) { result[info.name] = info.value; return; } if (Array.isArray(result[info.name]) && Array.isArray(info.value)) { result[info.name] = [].concat(result[info.name], info.value); return; } } /** * @param {HTMLInputElement} node * @returns {{name:string, value:string | string[]} | undefined} */ function getInputInfo(node) { try { /** @type {{name: string, value: string | string[]}}*/ var result = { name: node.name, value: node.value, }; if (result.name == undefined || result.value == undefined) { return undefined; } if (node.type == "radio" && node.checked == false) { return undefined; } if (node.type == "checkbox") { if (node.checked == false) { result.value = undefined; } else if (typeof result.value === "string") { result.value = [result.value]; } } if (node.type == "select-multiple") { /** @type {NodeListOf} */ var selected = node.querySelectorAll("option[selected]"); result.value = []; for (var index = 0; index < selected.length; index++) { result.value.push(selected[index].value); } } return result; } catch (e) { return undefined; } } }); config.conversions["HTML"] = function (value) { var toHTML = /** @returns {string}*/ function (/** @type any*/ value) { if (value instanceof Array) { return value .map(function (item) { return toHTML(item); }) .join(""); } if (value instanceof HTMLElement) { return value.outerHTML; } if (value instanceof NodeList) { var result = ""; for (var i = 0; i < value.length; i++) { var node = value[i]; if (node instanceof HTMLElement) { result += node.outerHTML; } } return result; } if (value.toString) { return value.toString(); } return ""; }; return toHTML(value); }; config.conversions["Fragment"] = function (val) { var frag = document.createDocumentFragment(); parser.runtime.implicitLoop(val, function (val) { if (val instanceof Node) frag.append(val); else { var temp = document.createElement("template"); temp.innerHTML = val; frag.append(temp.content); } }); return frag; }; } // Public API const runtime_ = new Runtime(), lexer_ = runtime_.lexer, parser_ = runtime_.parser /** * * @param {string} src * @param {Partial} [ctx] */ function run(src, ctx) { return runtime_.evaluate(src, ctx) } function browserInit() { /** @type {HTMLScriptElement[]} */ var scripts = Array.from(globalScope.document.querySelectorAll("script[type='text/hyperscript'][src]")) Promise.all( scripts.map(function (script) { return fetch(script.src) .then(function (res) { return res.text(); }); }) ) .then(script_values => script_values.forEach(sc => _hyperscript(sc))) .then(() => ready(function () { mergeMetaConfig(); runtime_.processNode(document.documentElement); globalScope.document.addEventListener("htmx:load", function (/** @type {CustomEvent} */ evt) { runtime_.processNode(evt.detail.elt); }); })); function ready(fn) { if (document.readyState !== "loading") { setTimeout(fn); } else { document.addEventListener("DOMContentLoaded", fn); } } function getMetaConfig() { /** @type {HTMLMetaElement} */ var element = document.querySelector('meta[name="htmx-config"]'); if (element) { return parseJSON(element.content); } else { return null; } } function mergeMetaConfig() { var metaConfig = getMetaConfig(); if (metaConfig) { Object.assign(config, metaConfig); } } } /** * @typedef {Object} HyperscriptAPI * * @property {Object} config * @property {string} config.attributes * @property {string} config.defaultTransition * @property {string} config.disableSelector * @property {typeof conversions} config.conversions * * @property {Object} internals * @property {Lexer} internals.lexer * @property {typeof Lexer} internals.Lexer * @property {Parser} internals.parser * @property {typeof Parser} internals.Parser * @property {Runtime} internals.runtime * @property {typeof Runtime} internals.Runtime * * @property {typeof ElementCollection} ElementCollection * * @property {(keyword: string, definition: ParseRule) => void} addFeature * @property {(keyword: string, definition: ParseRule) => void} addCommand * @property {(keyword: string, definition: ParseRule) => void} addLeafExpression * @property {(keyword: string, definition: ParseRule) => void} addIndirectExpression * * @property {(src: string, ctx?: Partial) => any} evaluate * @property {(src: string) => ASTNode} parse * @property {(node: Element) => void} processNode * * @property {() => void} browserInit * * * @typedef {HyperscriptAPI & ((src: string, ctx?: Partial) => any)} Hyperscript */ /** * @type {Hyperscript} */ const _hyperscript = Object.assign( run, { config, use(plugin) { plugin(_hyperscript) }, internals: { lexer: lexer_, parser: parser_, runtime: runtime_, Lexer, Tokens, Parser, Runtime, }, ElementCollection, addFeature: parser_.addFeature.bind(parser_), addCommand: parser_.addCommand.bind(parser_), addLeafExpression: parser_.addLeafExpression.bind(parser_), addIndirectExpression: parser_.addIndirectExpression.bind(parser_), evaluate: runtime_.evaluate.bind(runtime_), parse: runtime_.parse.bind(runtime_), processNode: runtime_.processNode.bind(runtime_), browserInit, } ) return _hyperscript })