From a26c4529062e4eb8123525596cdbf3650ea8bad8 Mon Sep 17 00:00:00 2001 From: Alex Kanunnikov Date: Fri, 5 Jan 2024 20:54:24 +0300 Subject: [PATCH 1/3] svg-part-two --- plugins/symbols.ts | 2 ++ plugins/utils.ts | 44 +++++++++++++++++++++++++++++++--- src/components/Application.gts | 3 +++ src/components/Icon.gts | 4 ++++ src/utils/dom-api.ts | 8 +++++++ src/utils/dom.ts | 13 ++++++++-- 6 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 src/components/Icon.gts diff --git a/plugins/symbols.ts b/plugins/symbols.ts index f45f7e15..756bdeb6 100644 --- a/plugins/symbols.ts +++ b/plugins/symbols.ts @@ -3,6 +3,8 @@ export const SYMBOLS = { TAG: '$_tag', IF: '$_if', EACH: '$_each', + ENTER_SVG: '$_enterSVG', + EXIT_SVG: '$_exitSVG', SLOT: '$_slot', ARGS: '$_args', TEXT: '$_text', diff --git a/plugins/utils.ts b/plugins/utils.ts index 267ca613..42066769 100644 --- a/plugins/utils.ts +++ b/plugins/utils.ts @@ -23,6 +23,41 @@ export type HBSNode = { children: (string | HBSNode | HBSControlExpression)[]; }; +const RESERVED_TAG_NAMES = [ + 'linearGradient', + 'radialGradient', + 'animateMotion', + 'animateTransform', + 'clipPath', + 'feBlend', + 'feColorMatrix', + 'feComponentTransfer', + 'feComposite', + 'feConvolveMatrix', + 'feDiffuseLighting', + 'feDisplacementMap', + 'feDistantLight', + 'feDropShadow', + 'feFlood', + 'feFuncA', + 'feFuncB', + 'feFuncR', + 'feFuncG', + 'feGaussianBlur', + 'feImage', + 'feMerge', + 'feMergeNode', + 'feMorphology', + 'feOffset', + 'fePointLight', + 'feSpecularLighting', + 'feSpotLight', + 'feTile', + 'feTurbulence', + 'foreignObject', + 'glyphRef' +] + export function escapeString(str: string) { const lines = str.split('\n'); if (lines.length === 1) { @@ -190,7 +225,7 @@ export function serializeNode( } else if ( typeof node === 'object' && node.tag && - node.tag.toLowerCase() !== node.tag + node.tag.toLowerCase() !== node.tag && !RESERVED_TAG_NAMES.includes(node.tag) ) { const hasSplatAttrs = node.attributes.find((attr) => { return attr[0] === '...attributes'; @@ -276,12 +311,15 @@ export function serializeNode( node.attributes = node.attributes.filter((attr) => { return attr[0] !== '...attributes'; }); - return `${SYMBOLS.TAG}('${node.tag}', [ + const isSvg = node.tag === "svg"; + const prefix = isSvg ? `${SYMBOLS.ENTER_SVG}(),` : ""; + const postfix = isSvg ? `,${SYMBOLS.EXIT_SVG}()` : ""; + return `${prefix}${SYMBOLS.TAG}('${node.tag}', [ ${toArray(node.properties)}, ${toArray(node.attributes)}, ${toArray(node.events)}, ${hasSplatAttrs ? `$fw` : ''} - ], [${serializeChildren(node.children)}])`; + ], [${serializeChildren(node.children)}])${postfix}`; } else { if (typeof node === 'string') { if (isPath(node)) { diff --git a/src/components/Application.gts b/src/components/Application.gts index 3eaa4f10..45b5a36b 100644 --- a/src/components/Application.gts +++ b/src/components/Application.gts @@ -5,6 +5,8 @@ import { renderComponent, runDestructors } from '@/utils/component'; import { Header } from './Header.gts'; import { Component } from '@/utils/component'; import { Row } from './Row.gts'; +import { Icon } from './Icon.gts'; + export class Application extends Component { itemsCell = cell([], 'items'); rootNode!: HTMLElement; @@ -66,6 +68,7 @@ export class Application extends Component { @swaprows={{this.actions.swaprows}} @runlots={{this.actions.runlots}} /> + {{#each this.itemsCell as |item|}} diff --git a/src/components/Icon.gts b/src/components/Icon.gts new file mode 100644 index 00000000..1dae1548 --- /dev/null +++ b/src/components/Icon.gts @@ -0,0 +1,4 @@ +export const Icon = \ No newline at end of file diff --git a/src/utils/dom-api.ts b/src/utils/dom-api.ts index f53292b3..b8677d2f 100644 --- a/src/utils/dom-api.ts +++ b/src/utils/dom-api.ts @@ -1,5 +1,10 @@ export const api = { attr(element: HTMLElement, name: string, value: string | null) { + if (name.includes(':')) { + const [ns, key] = name.split(':'); + element.setAttributeNS(ns, key, value as string); + return; + } element.setAttribute(name, value === null ? '' : value); }, comment(text = '') { @@ -17,6 +22,9 @@ export const api = { element(tagName = '') { return document.createElement(tagName); }, + svgElement(tagName = '') { + return document.createElementNS('http://www.w3.org/2000/svg', tagName); + }, append(parent: HTMLElement | Node, child: HTMLElement | Node) { parent.appendChild(child); }, diff --git a/src/utils/dom.ts b/src/utils/dom.ts index 47e4da32..c5426735 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -15,6 +15,8 @@ import { ifCondition } from '@/utils/if'; import { DestructorFn, Destructors, executeDestructors } from './destroyable'; import { api } from '@/utils/dom-api'; +let svgContextOpened = false; + type ModifierFn = ( element: HTMLElement, ...args: unknown[] @@ -172,7 +174,14 @@ const EVENT_TYPE = { ON_CREATED: '0', TEXT_CONTENT: '1', }; - +export function $_enterSVG() { + svgContextOpened = true; + return def(document.createComment("svg-context-start")); +} +export function $_exitSVG() { + svgContextOpened = false; + return def(document.createComment("svg-context-start")); +} function _DOM( tag: string, tagProps: Props, @@ -185,7 +194,7 @@ function _DOM( | Function )[], ): NodeReturnType { - const element = api.element(tag); + const element = svgContextOpened ? api.svgElement(tag) : api.element(tag); const destructors: Destructors = []; const props = tagProps[0]; const attrs = tagProps[1]; From 7dc08a816eebbe748b159c5f66f722296efc4fc6 Mon Sep 17 00:00:00 2001 From: Alex Kanunnikov Date: Fri, 5 Jan 2024 20:57:34 +0300 Subject: [PATCH 2/3] fix typings --- src/utils/component.ts | 2 +- src/utils/dom.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/component.ts b/src/utils/component.ts index 03d5aca3..370a87cd 100644 --- a/src/utils/component.ts +++ b/src/utils/component.ts @@ -175,7 +175,7 @@ function getNode(el: Node): Node { export function addDestructors( destructors: Destructors, - source: ComponentReturnType | NodeReturnType | HTMLElement | Text | Comment, + source: ComponentReturnType | NodeReturnType | HTMLElement | SVGElement | Text | Comment, ) { if (destructors.length === 0) { return; diff --git a/src/utils/dom.ts b/src/utils/dom.ts index c5426735..b7fe3097 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -18,7 +18,7 @@ import { api } from '@/utils/dom-api'; let svgContextOpened = false; type ModifierFn = ( - element: HTMLElement, + element: HTMLElement | SVGElement, ...args: unknown[] ) => void | DestructorFn; @@ -39,7 +39,7 @@ type FwType = { type Props = [TagProp[], TagAttr[], TagEvent[], FwType?]; function $prop( - element: HTMLElement, + element: HTMLElement | SVGElement, key: string, value: unknown, destructors: DestructorFn[], @@ -65,7 +65,7 @@ function $prop( } function $attr( - element: HTMLElement, + element: HTMLElement | SVGElement, key: string, value: unknown, destructors: Destructors, @@ -91,7 +91,7 @@ function $attr( } function addChild( - element: HTMLElement, + element: HTMLElement | SVGElement, child: | NodeReturnType | ComponentReturnType From 06481fc252f12b76487122590b1a3e6b2fe549a0 Mon Sep 17 00:00:00 2001 From: Alex Kanunnikov Date: Fri, 5 Jan 2024 21:22:41 +0300 Subject: [PATCH 3/3] fast svg case --- plugins/converter.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/plugins/converter.ts b/plugins/converter.ts index 12a4df2a..45c9b245 100644 --- a/plugins/converter.ts +++ b/plugins/converter.ts @@ -1,4 +1,5 @@ import type { ASTv1 } from '@glimmer/syntax'; +import { print, builders } from '@glimmer/syntax'; import { HBSControlExpression, HBSNode, @@ -175,7 +176,47 @@ export function convert(seenNodes: Set) { return !propertyKeys.includes(name); } + function isAllChildNodesSimpleElements(element: ASTv1.ElementNode) { + return element.children.every((child) => { + if (child.type === 'ElementNode') { + return ( + child.tag.charAt(0).toLowerCase() === child.tag.charAt(0) && + !child.tag.startsWith(':') + ); + } + return true; + }); + } + function ElementToNode(element: ASTv1.ElementNode): HBSNode { + if (element.tag === 'svg' && isAllChildNodesSimpleElements(element)) { + element.children.forEach((child) => { + seenNodes.add(child); + }); + + const inner = print(builders.template(element.children)); + element.children = []; + // console.log(print(element)); + return { + tag: element.tag, + blockParams: [], + selfClosing: element.selfClosing, + hasStableChild: true, + attributes: element.attributes + .filter((el) => isAttribute(el.name)) + .map((attr) => { + const rawValue = ToJSType(attr.value); + return [attr.name, rawValue]; + }), + properties: [], + events: [ + [EVENT_TYPE.ON_CREATED, `$:(el) => { + el.innerHTML = \`${inner}\` + }`] + ], + children: [], + }; + } const children = resolvedChildren(element.children) .map((el) => ToJSType(el)) .filter((el) => el !== null);