@symbo.ls/element 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './src/index.js'
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@symbo.ls/element",
3
+ "version": "3.14.0",
4
+ "description": "Core element creation, update, and disposal engine for smbls v4",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "module": "./index.js",
8
+ "exports": {
9
+ ".": "./index.js",
10
+ "./src/*": "./src/*"
11
+ },
12
+ "dependencies": {
13
+ "@symbo.ls/signal": "^3.14.0",
14
+ "@symbo.ls/state": "^3.14.0",
15
+ "@symbo.ls/utils": "^3.14.0",
16
+ "css-in-props": "^3.14.0",
17
+ "attrs-in-props": "^3.14.0"
18
+ },
19
+ "files": ["src", "index.js"],
20
+ "license": "CC-BY-NC-4.0"
21
+ }
package/src/cache.js ADDED
@@ -0,0 +1,124 @@
1
+ 'use strict'
2
+
3
+ // Node caching - cloneNode from cached prototypes instead of createElement
4
+ // This is already a v3 optimization that works well -- preserved in v4
5
+
6
+ const SVG_NS = 'http://www.w3.org/2000/svg'
7
+
8
+ const SVG_TAGS = new Set([
9
+ 'svg', 'path', 'circle', 'ellipse', 'line', 'polyline', 'polygon',
10
+ 'rect', 'g', 'defs', 'use', 'symbol', 'clipPath', 'mask',
11
+ 'pattern', 'image', 'text', 'tspan', 'textPath',
12
+ 'marker', 'linearGradient', 'radialGradient', 'stop',
13
+ 'filter', 'feBlend', 'feColorMatrix', 'feComponentTransfer',
14
+ 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
15
+ 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage',
16
+ 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset',
17
+ 'feSpecularLighting', 'feTile', 'feTurbulence',
18
+ 'animate', 'animateMotion', 'animateTransform', 'set',
19
+ 'foreignObject', 'desc', 'title', 'metadata'
20
+ ])
21
+
22
+ const VALID_HTML_TAGS = new Set([
23
+ 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio',
24
+ 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button',
25
+ 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup',
26
+ 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt',
27
+ 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form',
28
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr',
29
+ 'i', 'iframe', 'img', 'input', 'ins', 'kbd',
30
+ 'label', 'legend', 'li', 'link',
31
+ 'main', 'map', 'mark', 'menu', 'meta', 'meter',
32
+ 'nav', 'noscript',
33
+ 'object', 'ol', 'optgroup', 'option', 'output',
34
+ 'p', 'param', 'picture', 'pre', 'progress',
35
+ 'q', 'rp', 'rt', 'ruby',
36
+ 's', 'samp', 'script', 'search', 'section', 'select', 'slot', 'small',
37
+ 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup',
38
+ 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead',
39
+ 'time', 'title', 'tr', 'track',
40
+ 'u', 'ul', 'var', 'video', 'wbr',
41
+ // special
42
+ 'string', 'fragment', 'shadow'
43
+ ])
44
+
45
+ export function isValidTag(tag) {
46
+ return VALID_HTML_TAGS.has(tag) || SVG_TAGS.has(tag)
47
+ }
48
+
49
+ export function isSvgTag(tag) {
50
+ return SVG_TAGS.has(tag)
51
+ }
52
+
53
+ export function detectTag(element) {
54
+ let { tag, key } = element
55
+
56
+ if (typeof tag === 'function') tag = tag(element)
57
+ if (tag === true) tag = key
58
+
59
+ if (typeof tag === 'string' && isValidTag(tag)) return tag
60
+ if (!key || typeof key !== 'string') return 'div'
61
+
62
+ // Tags that should never be auto-detected from key names
63
+ const STRUCTURAL_TAGS = new Set([
64
+ 'html', 'head', 'body', 'meta', 'link', 'script', 'style',
65
+ 'title', 'base', 'noscript', 'template'
66
+ ])
67
+
68
+ // Try key as tag
69
+ let keyAsTag = key.toLowerCase()
70
+ if (keyAsTag.includes('.')) keyAsTag = keyAsTag.split('.')[0]
71
+ if (keyAsTag.includes('_')) keyAsTag = keyAsTag.split('_')[0]
72
+ if (isValidTag(keyAsTag) && !STRUCTURAL_TAGS.has(keyAsTag)) return keyAsTag
73
+
74
+ return 'div'
75
+ }
76
+
77
+ const nodeCaches = {}
78
+
79
+ export function createHTMLNode(tag, element) {
80
+ const doc = element?.context?.document || globalThis.document
81
+ if (!doc) return null
82
+
83
+ if (tag === 'string') return doc.createTextNode(element?.text || '')
84
+ if (tag === 'fragment') return doc.createDocumentFragment()
85
+
86
+ if (isSvgTag(tag)) {
87
+ const parent = element?.parent
88
+ const inSvgContext = tag === 'svg' || parent?.node?.namespaceURI === SVG_NS
89
+ if (inSvgContext) {
90
+ return doc.createElementNS(SVG_NS, tag)
91
+ }
92
+ // SVG child tags outside <svg> context — fall back to div
93
+ if (tag !== 'svg') return doc.createElement('div')
94
+ }
95
+
96
+ return doc.createElement(tag)
97
+ }
98
+
99
+ export function cacheNode(element) {
100
+ const tag = element.tag = detectTag(element)
101
+
102
+ const isSvgContext = isSvgTag(tag) &&
103
+ (tag === 'svg' || element.parent?.node?.namespaceURI === SVG_NS)
104
+ const cacheKey = isSvgContext ? 'svg:' + tag : tag
105
+
106
+ const targetDoc = element?.context?.document || globalThis.document
107
+
108
+ let cached = nodeCaches[cacheKey]
109
+ if (!cached) {
110
+ cached = nodeCaches[cacheKey] = createHTMLNode(tag, element)
111
+ }
112
+
113
+ if (!cached) return null
114
+
115
+ // If target document differs from cached node's document (e.g. iframe),
116
+ // create a fresh node instead of cloning to ensure correct ownerDocument
117
+ const cloned = (targetDoc && cached.ownerDocument !== targetDoc)
118
+ ? createHTMLNode(tag, element)
119
+ : cached.cloneNode(true)
120
+ if (tag === 'string') cloned.nodeValue = element.text || ''
121
+ return cloned
122
+ }
123
+
124
+ export { SVG_TAGS, SVG_NS }