@lytjs/common-dom 6.0.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/dist/index.cjs ADDED
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ var commonString = require('@lytjs/common-string');
4
+ var commonError = require('@lytjs/common-error');
5
+
6
+ // src/index.ts
7
+ var SVG_TAGS = /* @__PURE__ */ new Set([
8
+ // Basic shapes
9
+ "svg",
10
+ "path",
11
+ "circle",
12
+ "ellipse",
13
+ "line",
14
+ "polyline",
15
+ "polygon",
16
+ "rect",
17
+ // Groups & containers
18
+ "g",
19
+ "defs",
20
+ "use",
21
+ "clipPath",
22
+ "mask",
23
+ "symbol",
24
+ "marker",
25
+ "pattern",
26
+ "foreignObject",
27
+ "image",
28
+ // Text
29
+ "text",
30
+ "tspan",
31
+ "textPath",
32
+ // Gradients
33
+ "linearGradient",
34
+ "radialGradient",
35
+ "stop",
36
+ // Filters
37
+ "filter",
38
+ "feBlend",
39
+ "feColorMatrix",
40
+ "feComponentTransfer",
41
+ "feComposite",
42
+ "feConvolveMatrix",
43
+ "feDiffuseLighting",
44
+ "feDisplacementMap",
45
+ "feDistantLight",
46
+ "feFlood",
47
+ "feGaussianBlur",
48
+ "feImage",
49
+ "feMerge",
50
+ "feMergeNode",
51
+ "feMorphology",
52
+ "feOffset",
53
+ "fePointLight",
54
+ "feSpecularLighting",
55
+ "feSpotLight",
56
+ "feTile",
57
+ "feTurbulence",
58
+ // Animation
59
+ "animate",
60
+ "animateTransform",
61
+ "animateMotion",
62
+ "set"
63
+ ]);
64
+ var SVG_NS = "http://www.w3.org/2000/svg";
65
+ function isSVGTag(tag) {
66
+ return SVG_TAGS.has(tag);
67
+ }
68
+ function patchClass(el, prev, next) {
69
+ const el_ = el;
70
+ const prevClass = prev == null ? "" : String(prev);
71
+ const nextClass = next == null ? "" : String(next);
72
+ if (prevClass !== nextClass) {
73
+ el_.className = nextClass;
74
+ }
75
+ }
76
+ function patchStyle(el, prev, next) {
77
+ const el_ = el;
78
+ const style = el_.style;
79
+ if (!next || next === "") {
80
+ el_.removeAttribute("style");
81
+ return;
82
+ }
83
+ const prevStyle = prev;
84
+ const nextStyle = next;
85
+ if (nextStyle && typeof nextStyle !== "object" && typeof nextStyle !== "string") {
86
+ return;
87
+ }
88
+ if (typeof nextStyle === "string") {
89
+ if (prevStyle && typeof prevStyle !== "string") {
90
+ for (const key in prevStyle) {
91
+ style.removeProperty(commonString.camelToKebab(key));
92
+ }
93
+ }
94
+ el_.setAttribute("style", nextStyle);
95
+ return;
96
+ }
97
+ if (prevStyle && typeof prevStyle !== "string") {
98
+ for (const key in prevStyle) {
99
+ if (!(key in nextStyle)) {
100
+ style.removeProperty(commonString.camelToKebab(key));
101
+ }
102
+ }
103
+ } else if (typeof prevStyle === "string") {
104
+ el_.removeAttribute("style");
105
+ }
106
+ for (const key in nextStyle) {
107
+ const val = nextStyle[key];
108
+ if (val != null && val !== "") {
109
+ style.setProperty(commonString.camelToKebab(key), String(val));
110
+ } else {
111
+ style.removeProperty(commonString.camelToKebab(key));
112
+ }
113
+ }
114
+ }
115
+ function patchAttr(el, key, value, _isSVG) {
116
+ if (value == null || value === false) {
117
+ el.removeAttribute(key);
118
+ } else if (commonString.isBooleanAttr(key)) {
119
+ if (value === true || value === "") {
120
+ el.setAttribute(key, "");
121
+ } else {
122
+ const strValue = typeof value === "string" ? value : String(value);
123
+ if (!commonString.isSafeAttribute(key, strValue)) {
124
+ if (__DEV__) {
125
+ commonError.warn(`Unsafe attribute "${key}" with value "${strValue}" has been blocked.`);
126
+ }
127
+ return;
128
+ }
129
+ el.setAttribute(key, strValue);
130
+ }
131
+ } else {
132
+ const strValue = typeof value === "string" ? value : String(value);
133
+ if (!commonString.isSafeAttribute(key, strValue)) {
134
+ if (__DEV__) {
135
+ commonError.warn(`Unsafe attribute "${key}" with value "${strValue}" has been blocked.`);
136
+ }
137
+ return;
138
+ }
139
+ el.setAttribute(key, strValue);
140
+ }
141
+ }
142
+ function patchProp(el, key, prevValue, nextValue, isSVG = false) {
143
+ if (key === "class") {
144
+ patchClass(el, prevValue, nextValue);
145
+ } else if (key === "style") {
146
+ patchStyle(el, prevValue, nextValue);
147
+ } else if (key === "innerHTML") {
148
+ if (nextValue !== prevValue) {
149
+ if (__DEV__ && nextValue != null && typeof nextValue !== "string") {
150
+ commonError.warn("v-html expects a string value.");
151
+ }
152
+ const sanitized = nextValue == null ? "" : commonString.sanitizeHTML(String(nextValue));
153
+ el.innerHTML = sanitized;
154
+ }
155
+ } else if (key === "textContent") {
156
+ if (nextValue !== prevValue) {
157
+ el.textContent = nextValue == null ? "" : String(nextValue);
158
+ }
159
+ } else {
160
+ patchAttr(el, key, nextValue);
161
+ }
162
+ }
163
+
164
+ exports.SVG_NS = SVG_NS;
165
+ exports.SVG_TAGS = SVG_TAGS;
166
+ exports.isSVGTag = isSVGTag;
167
+ exports.patchAttr = patchAttr;
168
+ exports.patchClass = patchClass;
169
+ exports.patchProp = patchProp;
170
+ exports.patchStyle = patchStyle;
171
+ //# sourceMappingURL=index.cjs.map
172
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["camelToKebab","isBooleanAttr","isSafeAttribute","warn","sanitizeHTML"],"mappings":";;;;;;AAyBO,IAAM,QAAA,uBAAe,GAAA,CAAI;AAAA;AAAA,EAE9B,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,MAAA;AAAA;AAAA,EAEA,GAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,eAAA;AAAA,EACA,OAAA;AAAA;AAAA,EAEA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,UAAA;AAAA;AAAA,EAEA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA;AAAA,EAEA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,eAAA;AAAA,EACA,qBAAA;AAAA,EACA,aAAA;AAAA,EACA,kBAAA;AAAA,EACA,mBAAA;AAAA,EACA,mBAAA;AAAA,EACA,gBAAA;AAAA,EACA,SAAA;AAAA,EACA,gBAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,aAAA;AAAA,EACA,cAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAA;AAAA,EACA,oBAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA,cAAA;AAAA;AAAA,EAEA,SAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,MAAA,GAAS;AAKf,SAAS,SAAS,GAAA,EAAsB;AAC7C,EAAA,OAAO,QAAA,CAAS,IAAI,GAAG,CAAA;AACzB;AAQO,SAAS,UAAA,CAAW,EAAA,EAAa,IAAA,EAAe,IAAA,EAAqB;AAC1E,EAAA,MAAM,GAAA,GAAM,EAAA;AACZ,EAAA,MAAM,SAAA,GAAY,IAAA,IAAQ,IAAA,GAAO,EAAA,GAAK,OAAO,IAAI,CAAA;AACjD,EAAA,MAAM,SAAA,GAAY,IAAA,IAAQ,IAAA,GAAO,EAAA,GAAK,OAAO,IAAI,CAAA;AACjD,EAAA,IAAI,cAAc,SAAA,EAAW;AAC3B,IAAA,GAAA,CAAI,SAAA,GAAY,SAAA;AAAA,EAClB;AACF;AASO,SAAS,UAAA,CAAW,EAAA,EAAa,IAAA,EAAe,IAAA,EAAqB;AAC1E,EAAA,MAAM,GAAA,GAAM,EAAA;AACZ,EAAA,MAAM,QAAQ,GAAA,CAAI,KAAA;AAElB,EAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,KAAS,EAAA,EAAI;AACxB,IAAA,GAAA,CAAI,gBAAgB,OAAO,CAAA;AAC3B,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,GAAY,IAAA;AAClB,EAAA,MAAM,SAAA,GAAY,IAAA;AAElB,EAAA,IAAI,aAAa,OAAO,SAAA,KAAc,QAAA,IAAY,OAAO,cAAc,QAAA,EAAU;AAC/E,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,OAAO,cAAc,QAAA,EAAU;AACjC,IAAA,IAAI,SAAA,IAAa,OAAO,SAAA,KAAc,QAAA,EAAU;AAE9C,MAAA,KAAA,MAAW,OAAO,SAAA,EAAW;AAC3B,QAAA,KAAA,CAAM,cAAA,CAAeA,yBAAA,CAAa,GAAG,CAAC,CAAA;AAAA,MACxC;AAAA,IACF;AACA,IAAA,GAAA,CAAI,YAAA,CAAa,SAAS,SAAS,CAAA;AACnC,IAAA;AAAA,EACF;AAGA,EAAA,IAAI,SAAA,IAAa,OAAO,SAAA,KAAc,QAAA,EAAU;AAE9C,IAAA,KAAA,MAAW,OAAO,SAAA,EAAW;AAC3B,MAAA,IAAI,EAAE,OAAO,SAAA,CAAA,EAAY;AACvB,QAAA,KAAA,CAAM,cAAA,CAAeA,yBAAA,CAAa,GAAG,CAAC,CAAA;AAAA,MACxC;AAAA,IACF;AAAA,EACF,CAAA,MAAA,IAAW,OAAO,SAAA,KAAc,QAAA,EAAU;AAExC,IAAA,GAAA,CAAI,gBAAgB,OAAO,CAAA;AAAA,EAC7B;AAGA,EAAA,KAAA,MAAW,OAAO,SAAA,EAAW;AAC3B,IAAA,MAAM,GAAA,GAAM,UAAU,GAAG,CAAA;AACzB,IAAA,IAAI,GAAA,IAAO,IAAA,IAAQ,GAAA,KAAQ,EAAA,EAAI;AAC7B,MAAA,KAAA,CAAM,YAAYA,yBAAA,CAAa,GAAG,CAAA,EAAG,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,IAClD,CAAA,MAAO;AACL,MAAA,KAAA,CAAM,cAAA,CAAeA,yBAAA,CAAa,GAAG,CAAC,CAAA;AAAA,IACxC;AAAA,EACF;AACF;AAQO,SAAS,SAAA,CAAU,EAAA,EAAa,GAAA,EAAa,KAAA,EAAgB,MAAA,EAAuB;AACzF,EAAA,IAAI,KAAA,IAAS,IAAA,IAAQ,KAAA,KAAU,KAAA,EAAO;AACpC,IAAA,EAAA,CAAG,gBAAgB,GAAG,CAAA;AAAA,EACxB,CAAA,MAAA,IAAWC,0BAAA,CAAc,GAAG,CAAA,EAAG;AAC7B,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,EAAA,EAAI;AAClC,MAAA,EAAA,CAAG,YAAA,CAAa,KAAK,EAAE,CAAA;AAAA,IACzB,CAAA,MAAO;AACL,MAAA,MAAM,WAAW,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,OAAO,KAAK,CAAA;AACjE,MAAA,IAAI,CAACC,4BAAA,CAAgB,GAAA,EAAK,QAAQ,CAAA,EAAG;AACnC,QAAA,IAAI,OAAA,EAAS;AACX,UAAAC,gBAAA,CAAK,CAAA,kBAAA,EAAqB,GAAG,CAAA,cAAA,EAAiB,QAAQ,CAAA,mBAAA,CAAqB,CAAA;AAAA,QAC7E;AACA,QAAA;AAAA,MACF;AACA,MAAA,EAAA,CAAG,YAAA,CAAa,KAAK,QAAQ,CAAA;AAAA,IAC/B;AAAA,EACF,CAAA,MAAO;AACL,IAAA,MAAM,WAAW,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,OAAO,KAAK,CAAA;AACjE,IAAA,IAAI,CAACD,4BAAA,CAAgB,GAAA,EAAK,QAAQ,CAAA,EAAG;AACnC,MAAA,IAAI,OAAA,EAAS;AACX,QAAAC,gBAAA,CAAK,CAAA,kBAAA,EAAqB,GAAG,CAAA,cAAA,EAAiB,QAAQ,CAAA,mBAAA,CAAqB,CAAA;AAAA,MAC7E;AACA,MAAA;AAAA,IACF;AACA,IAAA,EAAA,CAAG,YAAA,CAAa,KAAK,QAAQ,CAAA;AAAA,EAC/B;AACF;AAYO,SAAS,UACd,EAAA,EACA,GAAA,EACA,SAAA,EACA,SAAA,EACA,QAAiB,KAAA,EACX;AACN,EAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,IAAA,UAAA,CAAW,EAAA,EAAI,WAAW,SAAS,CAAA;AAAA,EACrC,CAAA,MAAA,IAAW,QAAQ,OAAA,EAAS;AAC1B,IAAA,UAAA,CAAW,EAAA,EAAI,WAAW,SAAS,CAAA;AAAA,EACrC,CAAA,MAAA,IAAW,QAAQ,WAAA,EAAa;AAC9B,IAAA,IAAI,cAAc,SAAA,EAAW;AAC3B,MAAA,IAAI,OAAA,IAAW,SAAA,IAAa,IAAA,IAAQ,OAAO,cAAc,QAAA,EAAU;AACjE,QAAAA,gBAAA,CAAK,gCAAgC,CAAA;AAAA,MACvC;AACA,MAAA,MAAM,YAAY,SAAA,IAAa,IAAA,GAAO,KAAKC,yBAAA,CAAa,MAAA,CAAO,SAAS,CAAC,CAAA;AACzE,MAAA,EAAA,CAAG,SAAA,GAAY,SAAA;AAAA,IACjB;AAAA,EACF,CAAA,MAAA,IAAW,QAAQ,aAAA,EAAe;AAChC,IAAA,IAAI,cAAc,SAAA,EAAW;AAC3B,MAAA,EAAA,CAAG,WAAA,GAAc,SAAA,IAAa,IAAA,GAAO,EAAA,GAAK,OAAO,SAAS,CAAA;AAAA,IAC5D;AAAA,EACF,CAAA,MAAO;AACL,IAAA,SAAA,CAAU,EAAA,EAAI,GAAA,EAAK,SAAgB,CAAA;AAAA,EACrC;AACF","file":"index.cjs","sourcesContent":["/**\r\n * @lytjs/common-dom - Shared DOM utilities for Lyt.js\r\n *\r\n * Provides unified SVG tag detection, DOM property patching functions\r\n * (patchClass, patchStyle, patchAttr, patchProp) shared between\r\n * @lytjs/vdom and @lytjs/renderer.\r\n *\r\n * @module @lytjs/common-dom\r\n * @version 0.1.0\r\n */\r\n\r\n// ==================== Imports ====================\r\n\r\nimport { camelToKebab, isSafeAttribute, sanitizeHTML, isBooleanAttr } from '@lytjs/common-string';\r\nimport { warn } from '@lytjs/common-error';\r\n\r\n// Global dev flag declaration (injected by build tool)\r\ndeclare const __DEV__: boolean;\r\n\r\n// ==================== SVG Constants ====================\r\n\r\n/**\r\n * Complete set of SVG elements that require the SVG namespace.\r\n * Merged from @lytjs/vdom and @lytjs/renderer (union of both sets).\r\n */\r\nexport const SVG_TAGS = new Set([\r\n // Basic shapes\r\n 'svg',\r\n 'path',\r\n 'circle',\r\n 'ellipse',\r\n 'line',\r\n 'polyline',\r\n 'polygon',\r\n 'rect',\r\n // Groups & containers\r\n 'g',\r\n 'defs',\r\n 'use',\r\n 'clipPath',\r\n 'mask',\r\n 'symbol',\r\n 'marker',\r\n 'pattern',\r\n 'foreignObject',\r\n 'image',\r\n // Text\r\n 'text',\r\n 'tspan',\r\n 'textPath',\r\n // Gradients\r\n 'linearGradient',\r\n 'radialGradient',\r\n 'stop',\r\n // Filters\r\n 'filter',\r\n 'feBlend',\r\n 'feColorMatrix',\r\n 'feComponentTransfer',\r\n 'feComposite',\r\n 'feConvolveMatrix',\r\n 'feDiffuseLighting',\r\n 'feDisplacementMap',\r\n 'feDistantLight',\r\n 'feFlood',\r\n 'feGaussianBlur',\r\n 'feImage',\r\n 'feMerge',\r\n 'feMergeNode',\r\n 'feMorphology',\r\n 'feOffset',\r\n 'fePointLight',\r\n 'feSpecularLighting',\r\n 'feSpotLight',\r\n 'feTile',\r\n 'feTurbulence',\r\n // Animation\r\n 'animate',\r\n 'animateTransform',\r\n 'animateMotion',\r\n 'set',\r\n]);\r\n\r\n/** SVG namespace URI */\r\nexport const SVG_NS = 'http://www.w3.org/2000/svg';\r\n\r\n/**\r\n * Check if a tag name is an SVG element\r\n */\r\nexport function isSVGTag(tag: string): boolean {\r\n return SVG_TAGS.has(tag);\r\n}\r\n\r\n// ==================== patchClass ====================\r\n\r\n/**\r\n * Patch the class attribute on an element.\r\n * Uses String() conversion for robust null/undefined handling.\r\n */\r\nexport function patchClass(el: Element, prev: unknown, next: unknown): void {\r\n const el_ = el as HTMLElement;\r\n const prevClass = prev == null ? '' : String(prev);\r\n const nextClass = next == null ? '' : String(next);\r\n if (prevClass !== nextClass) {\r\n el_.className = nextClass;\r\n }\r\n}\r\n\r\n// ==================== patchStyle ====================\r\n\r\n/**\r\n * Patch the style attribute on an element.\r\n * Handles string <-> object transitions, camelCase <-> kebab-case conversion,\r\n * and proper property removal via removeProperty.\r\n */\r\nexport function patchStyle(el: Element, prev: unknown, next: unknown): void {\r\n const el_ = el as HTMLElement;\r\n const style = el_.style;\r\n\r\n if (!next || next === '') {\r\n el_.removeAttribute('style');\r\n return;\r\n }\r\n\r\n const prevStyle = prev as Record<string, string | number> | null | undefined;\r\n const nextStyle = next as Record<string, string | number> | string;\r\n\r\n if (nextStyle && typeof nextStyle !== 'object' && typeof nextStyle !== 'string') {\r\n return;\r\n }\r\n\r\n if (typeof nextStyle === 'string') {\r\n if (prevStyle && typeof prevStyle !== 'string') {\r\n // Was object, now string - clear all inline styles\r\n for (const key in prevStyle) {\r\n style.removeProperty(camelToKebab(key));\r\n }\r\n }\r\n el_.setAttribute('style', nextStyle);\r\n return;\r\n }\r\n\r\n // nextStyle is an object\r\n if (prevStyle && typeof prevStyle !== 'string') {\r\n // Remove keys that existed in prev but not in next\r\n for (const key in prevStyle) {\r\n if (!(key in nextStyle)) {\r\n style.removeProperty(camelToKebab(key));\r\n }\r\n }\r\n } else if (typeof prevStyle === 'string') {\r\n // Was string, now object - clear the string style\r\n el_.removeAttribute('style');\r\n }\r\n\r\n // Apply all new styles\r\n for (const key in nextStyle) {\r\n const val = nextStyle[key];\r\n if (val != null && val !== '') {\r\n style.setProperty(camelToKebab(key), String(val));\r\n } else {\r\n style.removeProperty(camelToKebab(key));\r\n }\r\n }\r\n}\r\n\r\n// ==================== patchAttr ====================\r\n\r\n/**\r\n * Patch a regular or boolean attribute on an element.\r\n * Includes safety checks via isSafeAttribute.\r\n */\r\nexport function patchAttr(el: Element, key: string, value: unknown, _isSVG: boolean): void {\r\n if (value == null || value === false) {\r\n el.removeAttribute(key);\r\n } else if (isBooleanAttr(key)) {\r\n if (value === true || value === '') {\r\n el.setAttribute(key, '');\r\n } else {\r\n const strValue = typeof value === 'string' ? value : String(value);\r\n if (!isSafeAttribute(key, strValue)) {\r\n if (__DEV__) {\r\n warn(`Unsafe attribute \"${key}\" with value \"${strValue}\" has been blocked.`);\r\n }\r\n return;\r\n }\r\n el.setAttribute(key, strValue);\r\n }\r\n } else {\r\n const strValue = typeof value === 'string' ? value : String(value);\r\n if (!isSafeAttribute(key, strValue)) {\r\n if (__DEV__) {\r\n warn(`Unsafe attribute \"${key}\" with value \"${strValue}\" has been blocked.`);\r\n }\r\n return;\r\n }\r\n el.setAttribute(key, strValue);\r\n }\r\n}\r\n\r\n// ==================== patchProp ====================\r\n\r\n/**\r\n * Patch a prop on a DOM element.\r\n * Dispatches to specialized handlers for class, style, events, and attributes.\r\n *\r\n * Note: Event handling is intentionally NOT included here. Consumers\r\n * (vdom/renderer) should handle events themselves based on their\r\n * chosen strategy (direct binding vs invoker pattern).\r\n */\r\nexport function patchProp(\r\n el: Element,\r\n key: string,\r\n prevValue: unknown,\r\n nextValue: unknown,\r\n isSVG: boolean = false,\r\n): void {\r\n if (key === 'class') {\r\n patchClass(el, prevValue, nextValue);\r\n } else if (key === 'style') {\r\n patchStyle(el, prevValue, nextValue);\r\n } else if (key === 'innerHTML') {\r\n if (nextValue !== prevValue) {\r\n if (__DEV__ && nextValue != null && typeof nextValue !== 'string') {\r\n warn('v-html expects a string value.');\r\n }\r\n const sanitized = nextValue == null ? '' : sanitizeHTML(String(nextValue));\r\n el.innerHTML = sanitized;\r\n }\r\n } else if (key === 'textContent') {\r\n if (nextValue !== prevValue) {\r\n el.textContent = nextValue == null ? '' : String(nextValue);\r\n }\r\n } else {\r\n patchAttr(el, key, nextValue, isSVG);\r\n }\r\n}\r\n"]}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @lytjs/common-dom - Shared DOM utilities for Lyt.js
3
+ *
4
+ * Provides unified SVG tag detection, DOM property patching functions
5
+ * (patchClass, patchStyle, patchAttr, patchProp) shared between
6
+ * @lytjs/vdom and @lytjs/renderer.
7
+ *
8
+ * @module @lytjs/common-dom
9
+ * @version 0.1.0
10
+ */
11
+ /**
12
+ * Complete set of SVG elements that require the SVG namespace.
13
+ * Merged from @lytjs/vdom and @lytjs/renderer (union of both sets).
14
+ */
15
+ declare const SVG_TAGS: Set<string>;
16
+ /** SVG namespace URI */
17
+ declare const SVG_NS = "http://www.w3.org/2000/svg";
18
+ /**
19
+ * Check if a tag name is an SVG element
20
+ */
21
+ declare function isSVGTag(tag: string): boolean;
22
+ /**
23
+ * Patch the class attribute on an element.
24
+ * Uses String() conversion for robust null/undefined handling.
25
+ */
26
+ declare function patchClass(el: Element, prev: unknown, next: unknown): void;
27
+ /**
28
+ * Patch the style attribute on an element.
29
+ * Handles string <-> object transitions, camelCase <-> kebab-case conversion,
30
+ * and proper property removal via removeProperty.
31
+ */
32
+ declare function patchStyle(el: Element, prev: unknown, next: unknown): void;
33
+ /**
34
+ * Patch a regular or boolean attribute on an element.
35
+ * Includes safety checks via isSafeAttribute.
36
+ */
37
+ declare function patchAttr(el: Element, key: string, value: unknown, _isSVG: boolean): void;
38
+ /**
39
+ * Patch a prop on a DOM element.
40
+ * Dispatches to specialized handlers for class, style, events, and attributes.
41
+ *
42
+ * Note: Event handling is intentionally NOT included here. Consumers
43
+ * (vdom/renderer) should handle events themselves based on their
44
+ * chosen strategy (direct binding vs invoker pattern).
45
+ */
46
+ declare function patchProp(el: Element, key: string, prevValue: unknown, nextValue: unknown, isSVG?: boolean): void;
47
+
48
+ export { SVG_NS, SVG_TAGS, isSVGTag, patchAttr, patchClass, patchProp, patchStyle };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @lytjs/common-dom - Shared DOM utilities for Lyt.js
3
+ *
4
+ * Provides unified SVG tag detection, DOM property patching functions
5
+ * (patchClass, patchStyle, patchAttr, patchProp) shared between
6
+ * @lytjs/vdom and @lytjs/renderer.
7
+ *
8
+ * @module @lytjs/common-dom
9
+ * @version 0.1.0
10
+ */
11
+ /**
12
+ * Complete set of SVG elements that require the SVG namespace.
13
+ * Merged from @lytjs/vdom and @lytjs/renderer (union of both sets).
14
+ */
15
+ declare const SVG_TAGS: Set<string>;
16
+ /** SVG namespace URI */
17
+ declare const SVG_NS = "http://www.w3.org/2000/svg";
18
+ /**
19
+ * Check if a tag name is an SVG element
20
+ */
21
+ declare function isSVGTag(tag: string): boolean;
22
+ /**
23
+ * Patch the class attribute on an element.
24
+ * Uses String() conversion for robust null/undefined handling.
25
+ */
26
+ declare function patchClass(el: Element, prev: unknown, next: unknown): void;
27
+ /**
28
+ * Patch the style attribute on an element.
29
+ * Handles string <-> object transitions, camelCase <-> kebab-case conversion,
30
+ * and proper property removal via removeProperty.
31
+ */
32
+ declare function patchStyle(el: Element, prev: unknown, next: unknown): void;
33
+ /**
34
+ * Patch a regular or boolean attribute on an element.
35
+ * Includes safety checks via isSafeAttribute.
36
+ */
37
+ declare function patchAttr(el: Element, key: string, value: unknown, _isSVG: boolean): void;
38
+ /**
39
+ * Patch a prop on a DOM element.
40
+ * Dispatches to specialized handlers for class, style, events, and attributes.
41
+ *
42
+ * Note: Event handling is intentionally NOT included here. Consumers
43
+ * (vdom/renderer) should handle events themselves based on their
44
+ * chosen strategy (direct binding vs invoker pattern).
45
+ */
46
+ declare function patchProp(el: Element, key: string, prevValue: unknown, nextValue: unknown, isSVG?: boolean): void;
47
+
48
+ export { SVG_NS, SVG_TAGS, isSVGTag, patchAttr, patchClass, patchProp, patchStyle };
package/dist/index.mjs ADDED
@@ -0,0 +1,164 @@
1
+ import { camelToKebab, isBooleanAttr, isSafeAttribute, sanitizeHTML } from '@lytjs/common-string';
2
+ import { warn } from '@lytjs/common-error';
3
+
4
+ // src/index.ts
5
+ var SVG_TAGS = /* @__PURE__ */ new Set([
6
+ // Basic shapes
7
+ "svg",
8
+ "path",
9
+ "circle",
10
+ "ellipse",
11
+ "line",
12
+ "polyline",
13
+ "polygon",
14
+ "rect",
15
+ // Groups & containers
16
+ "g",
17
+ "defs",
18
+ "use",
19
+ "clipPath",
20
+ "mask",
21
+ "symbol",
22
+ "marker",
23
+ "pattern",
24
+ "foreignObject",
25
+ "image",
26
+ // Text
27
+ "text",
28
+ "tspan",
29
+ "textPath",
30
+ // Gradients
31
+ "linearGradient",
32
+ "radialGradient",
33
+ "stop",
34
+ // Filters
35
+ "filter",
36
+ "feBlend",
37
+ "feColorMatrix",
38
+ "feComponentTransfer",
39
+ "feComposite",
40
+ "feConvolveMatrix",
41
+ "feDiffuseLighting",
42
+ "feDisplacementMap",
43
+ "feDistantLight",
44
+ "feFlood",
45
+ "feGaussianBlur",
46
+ "feImage",
47
+ "feMerge",
48
+ "feMergeNode",
49
+ "feMorphology",
50
+ "feOffset",
51
+ "fePointLight",
52
+ "feSpecularLighting",
53
+ "feSpotLight",
54
+ "feTile",
55
+ "feTurbulence",
56
+ // Animation
57
+ "animate",
58
+ "animateTransform",
59
+ "animateMotion",
60
+ "set"
61
+ ]);
62
+ var SVG_NS = "http://www.w3.org/2000/svg";
63
+ function isSVGTag(tag) {
64
+ return SVG_TAGS.has(tag);
65
+ }
66
+ function patchClass(el, prev, next) {
67
+ const el_ = el;
68
+ const prevClass = prev == null ? "" : String(prev);
69
+ const nextClass = next == null ? "" : String(next);
70
+ if (prevClass !== nextClass) {
71
+ el_.className = nextClass;
72
+ }
73
+ }
74
+ function patchStyle(el, prev, next) {
75
+ const el_ = el;
76
+ const style = el_.style;
77
+ if (!next || next === "") {
78
+ el_.removeAttribute("style");
79
+ return;
80
+ }
81
+ const prevStyle = prev;
82
+ const nextStyle = next;
83
+ if (nextStyle && typeof nextStyle !== "object" && typeof nextStyle !== "string") {
84
+ return;
85
+ }
86
+ if (typeof nextStyle === "string") {
87
+ if (prevStyle && typeof prevStyle !== "string") {
88
+ for (const key in prevStyle) {
89
+ style.removeProperty(camelToKebab(key));
90
+ }
91
+ }
92
+ el_.setAttribute("style", nextStyle);
93
+ return;
94
+ }
95
+ if (prevStyle && typeof prevStyle !== "string") {
96
+ for (const key in prevStyle) {
97
+ if (!(key in nextStyle)) {
98
+ style.removeProperty(camelToKebab(key));
99
+ }
100
+ }
101
+ } else if (typeof prevStyle === "string") {
102
+ el_.removeAttribute("style");
103
+ }
104
+ for (const key in nextStyle) {
105
+ const val = nextStyle[key];
106
+ if (val != null && val !== "") {
107
+ style.setProperty(camelToKebab(key), String(val));
108
+ } else {
109
+ style.removeProperty(camelToKebab(key));
110
+ }
111
+ }
112
+ }
113
+ function patchAttr(el, key, value, _isSVG) {
114
+ if (value == null || value === false) {
115
+ el.removeAttribute(key);
116
+ } else if (isBooleanAttr(key)) {
117
+ if (value === true || value === "") {
118
+ el.setAttribute(key, "");
119
+ } else {
120
+ const strValue = typeof value === "string" ? value : String(value);
121
+ if (!isSafeAttribute(key, strValue)) {
122
+ if (__DEV__) {
123
+ warn(`Unsafe attribute "${key}" with value "${strValue}" has been blocked.`);
124
+ }
125
+ return;
126
+ }
127
+ el.setAttribute(key, strValue);
128
+ }
129
+ } else {
130
+ const strValue = typeof value === "string" ? value : String(value);
131
+ if (!isSafeAttribute(key, strValue)) {
132
+ if (__DEV__) {
133
+ warn(`Unsafe attribute "${key}" with value "${strValue}" has been blocked.`);
134
+ }
135
+ return;
136
+ }
137
+ el.setAttribute(key, strValue);
138
+ }
139
+ }
140
+ function patchProp(el, key, prevValue, nextValue, isSVG = false) {
141
+ if (key === "class") {
142
+ patchClass(el, prevValue, nextValue);
143
+ } else if (key === "style") {
144
+ patchStyle(el, prevValue, nextValue);
145
+ } else if (key === "innerHTML") {
146
+ if (nextValue !== prevValue) {
147
+ if (__DEV__ && nextValue != null && typeof nextValue !== "string") {
148
+ warn("v-html expects a string value.");
149
+ }
150
+ const sanitized = nextValue == null ? "" : sanitizeHTML(String(nextValue));
151
+ el.innerHTML = sanitized;
152
+ }
153
+ } else if (key === "textContent") {
154
+ if (nextValue !== prevValue) {
155
+ el.textContent = nextValue == null ? "" : String(nextValue);
156
+ }
157
+ } else {
158
+ patchAttr(el, key, nextValue);
159
+ }
160
+ }
161
+
162
+ export { SVG_NS, SVG_TAGS, isSVGTag, patchAttr, patchClass, patchProp, patchStyle };
163
+ //# sourceMappingURL=index.mjs.map
164
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * @lytjs/common-dom - Shared DOM utilities for Lyt.js\r\n *\r\n * Provides unified SVG tag detection, DOM property patching functions\r\n * (patchClass, patchStyle, patchAttr, patchProp) shared between\r\n * @lytjs/vdom and @lytjs/renderer.\r\n *\r\n * @module @lytjs/common-dom\r\n * @version 0.1.0\r\n */\r\n\r\n// ==================== Imports ====================\r\n\r\nimport { camelToKebab, isSafeAttribute, sanitizeHTML, isBooleanAttr } from '@lytjs/common-string';\r\nimport { warn } from '@lytjs/common-error';\r\n\r\n// Global dev flag declaration (injected by build tool)\r\ndeclare const __DEV__: boolean;\r\n\r\n// ==================== SVG Constants ====================\r\n\r\n/**\r\n * Complete set of SVG elements that require the SVG namespace.\r\n * Merged from @lytjs/vdom and @lytjs/renderer (union of both sets).\r\n */\r\nexport const SVG_TAGS = new Set([\r\n // Basic shapes\r\n 'svg',\r\n 'path',\r\n 'circle',\r\n 'ellipse',\r\n 'line',\r\n 'polyline',\r\n 'polygon',\r\n 'rect',\r\n // Groups & containers\r\n 'g',\r\n 'defs',\r\n 'use',\r\n 'clipPath',\r\n 'mask',\r\n 'symbol',\r\n 'marker',\r\n 'pattern',\r\n 'foreignObject',\r\n 'image',\r\n // Text\r\n 'text',\r\n 'tspan',\r\n 'textPath',\r\n // Gradients\r\n 'linearGradient',\r\n 'radialGradient',\r\n 'stop',\r\n // Filters\r\n 'filter',\r\n 'feBlend',\r\n 'feColorMatrix',\r\n 'feComponentTransfer',\r\n 'feComposite',\r\n 'feConvolveMatrix',\r\n 'feDiffuseLighting',\r\n 'feDisplacementMap',\r\n 'feDistantLight',\r\n 'feFlood',\r\n 'feGaussianBlur',\r\n 'feImage',\r\n 'feMerge',\r\n 'feMergeNode',\r\n 'feMorphology',\r\n 'feOffset',\r\n 'fePointLight',\r\n 'feSpecularLighting',\r\n 'feSpotLight',\r\n 'feTile',\r\n 'feTurbulence',\r\n // Animation\r\n 'animate',\r\n 'animateTransform',\r\n 'animateMotion',\r\n 'set',\r\n]);\r\n\r\n/** SVG namespace URI */\r\nexport const SVG_NS = 'http://www.w3.org/2000/svg';\r\n\r\n/**\r\n * Check if a tag name is an SVG element\r\n */\r\nexport function isSVGTag(tag: string): boolean {\r\n return SVG_TAGS.has(tag);\r\n}\r\n\r\n// ==================== patchClass ====================\r\n\r\n/**\r\n * Patch the class attribute on an element.\r\n * Uses String() conversion for robust null/undefined handling.\r\n */\r\nexport function patchClass(el: Element, prev: unknown, next: unknown): void {\r\n const el_ = el as HTMLElement;\r\n const prevClass = prev == null ? '' : String(prev);\r\n const nextClass = next == null ? '' : String(next);\r\n if (prevClass !== nextClass) {\r\n el_.className = nextClass;\r\n }\r\n}\r\n\r\n// ==================== patchStyle ====================\r\n\r\n/**\r\n * Patch the style attribute on an element.\r\n * Handles string <-> object transitions, camelCase <-> kebab-case conversion,\r\n * and proper property removal via removeProperty.\r\n */\r\nexport function patchStyle(el: Element, prev: unknown, next: unknown): void {\r\n const el_ = el as HTMLElement;\r\n const style = el_.style;\r\n\r\n if (!next || next === '') {\r\n el_.removeAttribute('style');\r\n return;\r\n }\r\n\r\n const prevStyle = prev as Record<string, string | number> | null | undefined;\r\n const nextStyle = next as Record<string, string | number> | string;\r\n\r\n if (nextStyle && typeof nextStyle !== 'object' && typeof nextStyle !== 'string') {\r\n return;\r\n }\r\n\r\n if (typeof nextStyle === 'string') {\r\n if (prevStyle && typeof prevStyle !== 'string') {\r\n // Was object, now string - clear all inline styles\r\n for (const key in prevStyle) {\r\n style.removeProperty(camelToKebab(key));\r\n }\r\n }\r\n el_.setAttribute('style', nextStyle);\r\n return;\r\n }\r\n\r\n // nextStyle is an object\r\n if (prevStyle && typeof prevStyle !== 'string') {\r\n // Remove keys that existed in prev but not in next\r\n for (const key in prevStyle) {\r\n if (!(key in nextStyle)) {\r\n style.removeProperty(camelToKebab(key));\r\n }\r\n }\r\n } else if (typeof prevStyle === 'string') {\r\n // Was string, now object - clear the string style\r\n el_.removeAttribute('style');\r\n }\r\n\r\n // Apply all new styles\r\n for (const key in nextStyle) {\r\n const val = nextStyle[key];\r\n if (val != null && val !== '') {\r\n style.setProperty(camelToKebab(key), String(val));\r\n } else {\r\n style.removeProperty(camelToKebab(key));\r\n }\r\n }\r\n}\r\n\r\n// ==================== patchAttr ====================\r\n\r\n/**\r\n * Patch a regular or boolean attribute on an element.\r\n * Includes safety checks via isSafeAttribute.\r\n */\r\nexport function patchAttr(el: Element, key: string, value: unknown, _isSVG: boolean): void {\r\n if (value == null || value === false) {\r\n el.removeAttribute(key);\r\n } else if (isBooleanAttr(key)) {\r\n if (value === true || value === '') {\r\n el.setAttribute(key, '');\r\n } else {\r\n const strValue = typeof value === 'string' ? value : String(value);\r\n if (!isSafeAttribute(key, strValue)) {\r\n if (__DEV__) {\r\n warn(`Unsafe attribute \"${key}\" with value \"${strValue}\" has been blocked.`);\r\n }\r\n return;\r\n }\r\n el.setAttribute(key, strValue);\r\n }\r\n } else {\r\n const strValue = typeof value === 'string' ? value : String(value);\r\n if (!isSafeAttribute(key, strValue)) {\r\n if (__DEV__) {\r\n warn(`Unsafe attribute \"${key}\" with value \"${strValue}\" has been blocked.`);\r\n }\r\n return;\r\n }\r\n el.setAttribute(key, strValue);\r\n }\r\n}\r\n\r\n// ==================== patchProp ====================\r\n\r\n/**\r\n * Patch a prop on a DOM element.\r\n * Dispatches to specialized handlers for class, style, events, and attributes.\r\n *\r\n * Note: Event handling is intentionally NOT included here. Consumers\r\n * (vdom/renderer) should handle events themselves based on their\r\n * chosen strategy (direct binding vs invoker pattern).\r\n */\r\nexport function patchProp(\r\n el: Element,\r\n key: string,\r\n prevValue: unknown,\r\n nextValue: unknown,\r\n isSVG: boolean = false,\r\n): void {\r\n if (key === 'class') {\r\n patchClass(el, prevValue, nextValue);\r\n } else if (key === 'style') {\r\n patchStyle(el, prevValue, nextValue);\r\n } else if (key === 'innerHTML') {\r\n if (nextValue !== prevValue) {\r\n if (__DEV__ && nextValue != null && typeof nextValue !== 'string') {\r\n warn('v-html expects a string value.');\r\n }\r\n const sanitized = nextValue == null ? '' : sanitizeHTML(String(nextValue));\r\n el.innerHTML = sanitized;\r\n }\r\n } else if (key === 'textContent') {\r\n if (nextValue !== prevValue) {\r\n el.textContent = nextValue == null ? '' : String(nextValue);\r\n }\r\n } else {\r\n patchAttr(el, key, nextValue, isSVG);\r\n }\r\n}\r\n"]}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@lytjs/common-dom",
3
+ "version": "6.0.0",
4
+ "description": "Shared DOM utilities for Lyt.js - SVG tags, patchClass, patchStyle, patchAttr, patchProp",
5
+ "keywords": [
6
+ "lytjs",
7
+ "dom",
8
+ "svg",
9
+ "patch"
10
+ ],
11
+ "license": "MIT",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.mjs",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.mjs",
19
+ "require": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest",
33
+ "clean": "rm -rf dist .tsbuildinfo"
34
+ },
35
+ "dependencies": {
36
+ "@lytjs/common-is": "^6.0.0",
37
+ "@lytjs/common-string": "^6.0.0",
38
+ "@lytjs/common-events": "^6.0.0",
39
+ "@lytjs/common-error": "^6.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "^5.8.2",
43
+ "tsup": "^8.4.0",
44
+ "vitest": "^3.0.0",
45
+ "jsdom": "^24.0.0"
46
+ }
47
+ }