@cajoa/dom 0.7.0 → 0.9.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 CHANGED
@@ -32,14 +32,159 @@ module.exports = __toCommonJS(index_exports);
32
32
  var import_core2 = require("@cajoa/core");
33
33
 
34
34
  // src/dom-renderer.ts
35
+ var SVG_NS = "http://www.w3.org/2000/svg";
36
+ var SVG_TAGS = /* @__PURE__ */ new Set([
37
+ "svg",
38
+ "g",
39
+ "defs",
40
+ "symbol",
41
+ "use",
42
+ "switch",
43
+ "view",
44
+ "desc",
45
+ "metadata",
46
+ "circle",
47
+ "ellipse",
48
+ "line",
49
+ "path",
50
+ "polygon",
51
+ "polyline",
52
+ "rect",
53
+ "image",
54
+ "text",
55
+ "tspan",
56
+ "textPath",
57
+ "foreignObject",
58
+ "linearGradient",
59
+ "radialGradient",
60
+ "stop",
61
+ "pattern",
62
+ "clipPath",
63
+ "mask",
64
+ "marker",
65
+ "filter",
66
+ "feBlend",
67
+ "feColorMatrix",
68
+ "feComponentTransfer",
69
+ "feComposite",
70
+ "feConvolveMatrix",
71
+ "feDiffuseLighting",
72
+ "feDisplacementMap",
73
+ "feDropShadow",
74
+ "feFlood",
75
+ "feGaussianBlur",
76
+ "feImage",
77
+ "feMerge",
78
+ "feMergeNode",
79
+ "feMorphology",
80
+ "feOffset",
81
+ "feSpecularLighting",
82
+ "feTile",
83
+ "feTurbulence",
84
+ "animate",
85
+ "animateMotion",
86
+ "animateTransform",
87
+ "set",
88
+ "mpath"
89
+ ]);
90
+ var PROPERTIES = /* @__PURE__ */ new Set(["value", "checked", "selected", "indeterminate"]);
91
+ var ATTRIBUTE_NAMES = {
92
+ className: "class",
93
+ htmlFor: "for"
94
+ };
95
+ var SVG_ATTRIBUTE_NAMES = {
96
+ strokeWidth: "stroke-width",
97
+ strokeLinecap: "stroke-linecap",
98
+ strokeLinejoin: "stroke-linejoin",
99
+ strokeDasharray: "stroke-dasharray",
100
+ strokeDashoffset: "stroke-dashoffset",
101
+ strokeOpacity: "stroke-opacity",
102
+ strokeMiterlimit: "stroke-miterlimit",
103
+ fillOpacity: "fill-opacity",
104
+ fillRule: "fill-rule",
105
+ clipRule: "clip-rule",
106
+ clipPath: "clip-path",
107
+ stopColor: "stop-color",
108
+ stopOpacity: "stop-opacity",
109
+ floodColor: "flood-color",
110
+ floodOpacity: "flood-opacity",
111
+ textAnchor: "text-anchor",
112
+ dominantBaseline: "dominant-baseline",
113
+ fontSize: "font-size",
114
+ fontFamily: "font-family",
115
+ fontWeight: "font-weight",
116
+ letterSpacing: "letter-spacing",
117
+ markerStart: "marker-start",
118
+ markerMid: "marker-mid",
119
+ markerEnd: "marker-end",
120
+ vectorEffect: "vector-effect",
121
+ shapeRendering: "shape-rendering",
122
+ pointerEvents: "pointer-events"
123
+ };
124
+ var EVENT_NAMES = {
125
+ doubleclick: "dblclick"
126
+ };
127
+ var listeners = /* @__PURE__ */ new WeakMap();
128
+ var selectValues = /* @__PURE__ */ new WeakMap();
129
+ function isEvent(el, key) {
130
+ return /^on[A-Z]/.test(key) || key.startsWith("on") && key in el;
131
+ }
132
+ function setEvent(el, key, value) {
133
+ const raw = key.slice(2).toLowerCase();
134
+ const name = EVENT_NAMES[raw] ?? raw;
135
+ let byName = listeners.get(el);
136
+ if (!byName) {
137
+ byName = /* @__PURE__ */ new Map();
138
+ listeners.set(el, byName);
139
+ }
140
+ const previous = byName.get(name);
141
+ if (previous) {
142
+ el.removeEventListener(name, previous);
143
+ byName.delete(name);
144
+ }
145
+ if (typeof value === "function") {
146
+ el.addEventListener(name, value);
147
+ byName.set(name, value);
148
+ }
149
+ }
150
+ function toCssName(name) {
151
+ if (name.startsWith("--") || name.includes("-")) return name;
152
+ return name.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
153
+ }
154
+ function setStyle(el, value) {
155
+ const style = el.style;
156
+ if (typeof value === "string") {
157
+ style.cssText = value;
158
+ return;
159
+ }
160
+ style.cssText = "";
161
+ if (value && typeof value === "object") {
162
+ for (const [name, v] of Object.entries(value)) {
163
+ if (v == null || v === false || v === "") continue;
164
+ style.setProperty(toCssName(name), String(v));
165
+ }
166
+ }
167
+ }
168
+ function setProperty(el, name, value) {
169
+ const target = el;
170
+ if (name === "value") {
171
+ const next = value == null ? "" : String(value);
172
+ if (target.value !== next) target.value = next;
173
+ if (el.tagName === "SELECT") selectValues.set(el, next);
174
+ } else {
175
+ target[name] = !!value;
176
+ }
177
+ }
35
178
  var domRenderer = {
36
- createElement: (tag) => document.createElement(tag),
179
+ createElement: (tag) => SVG_TAGS.has(tag) ? document.createElementNS(SVG_NS, tag) : document.createElement(tag),
37
180
  createText: (value) => document.createTextNode(value),
38
181
  createMarker: () => document.createComment(""),
39
182
  createFragment: () => document.createDocumentFragment(),
40
183
  isNode: (v) => typeof Node !== "undefined" && v instanceof Node,
41
184
  insert: (parent, node, anchor = null) => {
42
185
  parent.insertBefore(node, anchor);
186
+ const wanted = selectValues.get(parent);
187
+ if (wanted !== void 0) parent.value = wanted;
43
188
  },
44
189
  remove: (node) => {
45
190
  node.remove();
@@ -51,11 +196,20 @@ var domRenderer = {
51
196
  },
52
197
  setProp(el, key, value) {
53
198
  const e = el;
54
- if (key.startsWith("on") && typeof value === "function") {
55
- e.addEventListener(key.slice(2).toLowerCase(), value);
199
+ if (isEvent(e, key)) {
200
+ setEvent(e, key, value);
201
+ return;
202
+ }
203
+ if (key === "style") {
204
+ setStyle(e, value);
205
+ return;
206
+ }
207
+ if (PROPERTIES.has(key) && key in e) {
208
+ setProperty(e, key, value);
56
209
  return;
57
210
  }
58
- const name = key === "className" ? "class" : key;
211
+ const isSvg = e.namespaceURI === SVG_NS;
212
+ const name = (isSvg ? SVG_ATTRIBUTE_NAMES[key] : void 0) ?? ATTRIBUTE_NAMES[key] ?? key;
59
213
  if (value == null || value === false) e.removeAttribute(name);
60
214
  else e.setAttribute(name, value === true ? "" : String(value));
61
215
  }
package/dist/index.js CHANGED
@@ -2,14 +2,159 @@
2
2
  import { setRenderer } from "@cajoa/core";
3
3
 
4
4
  // src/dom-renderer.ts
5
+ var SVG_NS = "http://www.w3.org/2000/svg";
6
+ var SVG_TAGS = /* @__PURE__ */ new Set([
7
+ "svg",
8
+ "g",
9
+ "defs",
10
+ "symbol",
11
+ "use",
12
+ "switch",
13
+ "view",
14
+ "desc",
15
+ "metadata",
16
+ "circle",
17
+ "ellipse",
18
+ "line",
19
+ "path",
20
+ "polygon",
21
+ "polyline",
22
+ "rect",
23
+ "image",
24
+ "text",
25
+ "tspan",
26
+ "textPath",
27
+ "foreignObject",
28
+ "linearGradient",
29
+ "radialGradient",
30
+ "stop",
31
+ "pattern",
32
+ "clipPath",
33
+ "mask",
34
+ "marker",
35
+ "filter",
36
+ "feBlend",
37
+ "feColorMatrix",
38
+ "feComponentTransfer",
39
+ "feComposite",
40
+ "feConvolveMatrix",
41
+ "feDiffuseLighting",
42
+ "feDisplacementMap",
43
+ "feDropShadow",
44
+ "feFlood",
45
+ "feGaussianBlur",
46
+ "feImage",
47
+ "feMerge",
48
+ "feMergeNode",
49
+ "feMorphology",
50
+ "feOffset",
51
+ "feSpecularLighting",
52
+ "feTile",
53
+ "feTurbulence",
54
+ "animate",
55
+ "animateMotion",
56
+ "animateTransform",
57
+ "set",
58
+ "mpath"
59
+ ]);
60
+ var PROPERTIES = /* @__PURE__ */ new Set(["value", "checked", "selected", "indeterminate"]);
61
+ var ATTRIBUTE_NAMES = {
62
+ className: "class",
63
+ htmlFor: "for"
64
+ };
65
+ var SVG_ATTRIBUTE_NAMES = {
66
+ strokeWidth: "stroke-width",
67
+ strokeLinecap: "stroke-linecap",
68
+ strokeLinejoin: "stroke-linejoin",
69
+ strokeDasharray: "stroke-dasharray",
70
+ strokeDashoffset: "stroke-dashoffset",
71
+ strokeOpacity: "stroke-opacity",
72
+ strokeMiterlimit: "stroke-miterlimit",
73
+ fillOpacity: "fill-opacity",
74
+ fillRule: "fill-rule",
75
+ clipRule: "clip-rule",
76
+ clipPath: "clip-path",
77
+ stopColor: "stop-color",
78
+ stopOpacity: "stop-opacity",
79
+ floodColor: "flood-color",
80
+ floodOpacity: "flood-opacity",
81
+ textAnchor: "text-anchor",
82
+ dominantBaseline: "dominant-baseline",
83
+ fontSize: "font-size",
84
+ fontFamily: "font-family",
85
+ fontWeight: "font-weight",
86
+ letterSpacing: "letter-spacing",
87
+ markerStart: "marker-start",
88
+ markerMid: "marker-mid",
89
+ markerEnd: "marker-end",
90
+ vectorEffect: "vector-effect",
91
+ shapeRendering: "shape-rendering",
92
+ pointerEvents: "pointer-events"
93
+ };
94
+ var EVENT_NAMES = {
95
+ doubleclick: "dblclick"
96
+ };
97
+ var listeners = /* @__PURE__ */ new WeakMap();
98
+ var selectValues = /* @__PURE__ */ new WeakMap();
99
+ function isEvent(el, key) {
100
+ return /^on[A-Z]/.test(key) || key.startsWith("on") && key in el;
101
+ }
102
+ function setEvent(el, key, value) {
103
+ const raw = key.slice(2).toLowerCase();
104
+ const name = EVENT_NAMES[raw] ?? raw;
105
+ let byName = listeners.get(el);
106
+ if (!byName) {
107
+ byName = /* @__PURE__ */ new Map();
108
+ listeners.set(el, byName);
109
+ }
110
+ const previous = byName.get(name);
111
+ if (previous) {
112
+ el.removeEventListener(name, previous);
113
+ byName.delete(name);
114
+ }
115
+ if (typeof value === "function") {
116
+ el.addEventListener(name, value);
117
+ byName.set(name, value);
118
+ }
119
+ }
120
+ function toCssName(name) {
121
+ if (name.startsWith("--") || name.includes("-")) return name;
122
+ return name.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
123
+ }
124
+ function setStyle(el, value) {
125
+ const style = el.style;
126
+ if (typeof value === "string") {
127
+ style.cssText = value;
128
+ return;
129
+ }
130
+ style.cssText = "";
131
+ if (value && typeof value === "object") {
132
+ for (const [name, v] of Object.entries(value)) {
133
+ if (v == null || v === false || v === "") continue;
134
+ style.setProperty(toCssName(name), String(v));
135
+ }
136
+ }
137
+ }
138
+ function setProperty(el, name, value) {
139
+ const target = el;
140
+ if (name === "value") {
141
+ const next = value == null ? "" : String(value);
142
+ if (target.value !== next) target.value = next;
143
+ if (el.tagName === "SELECT") selectValues.set(el, next);
144
+ } else {
145
+ target[name] = !!value;
146
+ }
147
+ }
5
148
  var domRenderer = {
6
- createElement: (tag) => document.createElement(tag),
149
+ createElement: (tag) => SVG_TAGS.has(tag) ? document.createElementNS(SVG_NS, tag) : document.createElement(tag),
7
150
  createText: (value) => document.createTextNode(value),
8
151
  createMarker: () => document.createComment(""),
9
152
  createFragment: () => document.createDocumentFragment(),
10
153
  isNode: (v) => typeof Node !== "undefined" && v instanceof Node,
11
154
  insert: (parent, node, anchor = null) => {
12
155
  parent.insertBefore(node, anchor);
156
+ const wanted = selectValues.get(parent);
157
+ if (wanted !== void 0) parent.value = wanted;
13
158
  },
14
159
  remove: (node) => {
15
160
  node.remove();
@@ -21,11 +166,20 @@ var domRenderer = {
21
166
  },
22
167
  setProp(el, key, value) {
23
168
  const e = el;
24
- if (key.startsWith("on") && typeof value === "function") {
25
- e.addEventListener(key.slice(2).toLowerCase(), value);
169
+ if (isEvent(e, key)) {
170
+ setEvent(e, key, value);
171
+ return;
172
+ }
173
+ if (key === "style") {
174
+ setStyle(e, value);
175
+ return;
176
+ }
177
+ if (PROPERTIES.has(key) && key in e) {
178
+ setProperty(e, key, value);
26
179
  return;
27
180
  }
28
- const name = key === "className" ? "class" : key;
181
+ const isSvg = e.namespaceURI === SVG_NS;
182
+ const name = (isSvg ? SVG_ATTRIBUTE_NAMES[key] : void 0) ?? ATTRIBUTE_NAMES[key] ?? key;
29
183
  if (value == null || value === false) e.removeAttribute(name);
30
184
  else e.setAttribute(name, value === true ? "" : String(value));
31
185
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cajoa/dom",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Cajoa for the browser: DOM renderer and router.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "sideEffects": true,
28
28
  "peerDependencies": {
29
- "@cajoa/core": "^0.7.0"
29
+ "@cajoa/core": "^0.9.0"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup src/index.ts --format esm,cjs --dts --clean",