@ktjs/core 0.22.5 → 0.24.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.
@@ -1,9 +1,34 @@
1
+ // Shared constants
2
+ // Empty for now - can be extended with framework-wide constants
3
+ /**
4
+ * Mark the attribute as SVG to handle special cases during rendering.
5
+ */
6
+ const SVG_ATTR_FLAG = '__kt_svg__';
7
+ /**
8
+ * Mark the attribute as MathML to handle special cases during rendering.
9
+ */
10
+ const MATHML_ATTR_FLAG = '__kt_mathml__';
11
+
1
12
  // Cached native methods for performance optimization
2
13
  const $isArray = Array.isArray;
3
- const $isThenable = (o) => typeof o === 'object' && o !== null && typeof o.then === 'function';
14
+ const $is = Object.is;
15
+ const $random = Math.random;
16
+ const $isThenable = (o) => typeof o?.then === 'function';
4
17
 
5
18
  // DOM manipulation utilities
6
19
  // # dom natives
20
+ const $isNode = (x) => x?.nodeType > 0;
21
+ /**
22
+ * Safe replace `oldNode` With `newNode`
23
+ */
24
+ const $replaceNode = (oldNode, newNode) => {
25
+ if ($isNode(oldNode) && $isNode(newNode)) {
26
+ if (newNode.contains(oldNode)) {
27
+ newNode.remove();
28
+ }
29
+ oldNode.replaceWith(newNode);
30
+ }
31
+ };
7
32
  /**
8
33
  * & Remove `bind` because it is shockingly slower than wrapper
9
34
  * & `window.document` is safe because it is not configurable and its setter is undefined
@@ -49,84 +74,21 @@ const applyModel = (element, valueRef, propName, eventName) => {
49
74
  element.addEventListener(eventName, () => (valueRef.value = element[propName]));
50
75
  };
51
76
 
77
+ if (typeof Symbol === 'undefined') {
78
+ window.Symbol = function Symbol(description) {
79
+ return `@@SYMBOL_${description || ''}_${$random().toString(36).slice(2)}`;
80
+ };
81
+ }
82
+
52
83
  // Shared utilities and cached native methods for kt.js framework
53
- // Re-export all utilities
54
- Object.defineProperty(window, '__ktjs__', { value: '0.22.2' });
84
+ Object.defineProperty(window, '__ktjs__', { value: '0.22.6' });
55
85
 
56
- class KTRef {
57
- /**
58
- * Indicates that this is a KTRef instance
59
- */
60
- isKT = true;
61
- /**
62
- * @internal
63
- */
64
- _value;
65
- /**
66
- * @internal
67
- */
68
- _onChanges;
69
- constructor(_value, _onChanges) {
70
- this._value = _value;
71
- this._onChanges = _onChanges;
72
- }
73
- /**
74
- * If new value and old value are both nodes, the old one will be replaced in the DOM
75
- */
76
- get value() {
77
- return this._value;
78
- }
79
- set value(newValue) {
80
- if (newValue === this._value) {
81
- return;
82
- }
83
- // replace the old node with the new one in the DOM if both are nodes
84
- if (this._value instanceof Node && newValue instanceof Node) {
85
- if (newValue.contains(this._value)) {
86
- this._value.remove();
87
- }
88
- this._value.replaceWith(newValue);
89
- }
90
- const oldValue = this._value;
91
- this._value = newValue;
92
- for (let i = 0; i < this._onChanges.length; i++) {
93
- this._onChanges[i](newValue, oldValue);
94
- }
95
- }
96
- /**
97
- * Register a callback when the value changes
98
- * @param callback (newValue, oldValue) => xxx
99
- */
100
- addOnChange(callback) {
101
- if (typeof callback !== 'function') {
102
- throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
103
- }
104
- this._onChanges.push(callback);
105
- }
106
- removeOnChange(callback) {
107
- for (let i = this._onChanges.length - 1; i >= 0; i--) {
108
- if (this._onChanges[i] === callback) {
109
- this._onChanges.splice(i, 1);
110
- return true;
111
- }
112
- }
113
- return false;
114
- }
115
- }
116
- const isKTRef = (obj) => obj?.isKT === true;
117
- /**
118
- * Reference to the created HTML element.
119
- * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
120
- * - can alse be used to store normal values, but it is not reactive.
121
- * - if the value is already a `KTRef`, it will be returned **directly**.
122
- * @param value mostly an HTMLElement
123
- */
124
- function ref(value, onChange) {
125
- if (isKTRef(value)) {
126
- return value;
127
- }
128
- return new KTRef(value, []);
129
- }
86
+ // export const KT_TYPE_REF = 1 as const;
87
+ // export const KT_TYPE_COMPUTED = 2 as const;
88
+ // export type KTReactiveTypeEnum = typeof KT_TYPE_REF | typeof KT_TYPE_COMPUTED;
89
+ const isKT = (obj) => obj?.isKT;
90
+ const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
91
+ const isComputed = (obj) => obj?.ktType === 2 /* KTReactiveType.COMPUTED */;
130
92
 
131
93
  const booleanHandler = (element, key, value) => {
132
94
  if (key in element) {
@@ -186,14 +148,25 @@ function attrIsObject(element, attr) {
186
148
  }
187
149
  }
188
150
  }
151
+ if ('k-html' in attr) {
152
+ const html = attr['k-html'];
153
+ if (isKT(html)) {
154
+ element.innerHTML = html.value;
155
+ html.addOnChange((v) => (element.innerHTML = v));
156
+ }
157
+ else {
158
+ element.innerHTML = html;
159
+ }
160
+ }
189
161
  for (const key in attr) {
190
- if (key === 'class' ||
162
+ if (key === 'k-if' ||
163
+ key === 'k-model' ||
164
+ key === 'ref' ||
165
+ key === 'class' ||
191
166
  key === 'className' ||
192
167
  key === 'style' ||
193
168
  key === 'children' ||
194
- key === 'k-if' ||
195
- key.startsWith('k-model') ||
196
- key === 'ref') {
169
+ key === 'k-html') {
197
170
  continue;
198
171
  }
199
172
  const o = attr[key];
@@ -203,6 +176,7 @@ function attrIsObject(element, attr) {
203
176
  }
204
177
  // normal attributes
205
178
  else {
179
+ // todo 这里也可以绑定ref的
206
180
  (handlers[key] || defaultHandler)(element, key, o);
207
181
  }
208
182
  }
@@ -219,13 +193,25 @@ function applyAttr(element, attr) {
219
193
  }
220
194
  }
221
195
 
196
+ const assureNode = (o) => ($isNode(o) ? o : document.createTextNode(o));
222
197
  function apdSingle(element, c) {
223
198
  // & JSX should ignore false, undefined, and null
224
199
  if (c === false || c === undefined || c === null) {
225
200
  return;
226
201
  }
227
- if (typeof c === 'object' && c !== null && 'isKT' in c) {
228
- $append.call(element, c.value);
202
+ if (isKT(c)) {
203
+ let node = assureNode(c.value);
204
+ $append.call(element, node);
205
+ c.addOnChange((newValue, oldValue) => {
206
+ if ($isNode(newValue) && $isNode(oldValue)) {
207
+ // & this case is handled automically in `class KTRef`
208
+ // todo 2 cases might be able to merge into 1
209
+ return;
210
+ }
211
+ const oldNode = node;
212
+ node = assureNode(newValue);
213
+ oldNode.replaceWith(node);
214
+ });
229
215
  }
230
216
  else {
231
217
  $append.call(element, c);
@@ -270,8 +256,74 @@ function applyContent(element, content) {
270
256
  }
271
257
  }
272
258
 
259
+ class KTRef {
260
+ /**
261
+ * Indicates that this is a KTRef instance
262
+ */
263
+ isKT = true;
264
+ ktType = 1 /* KTReactiveType.REF */;
265
+ /**
266
+ * @internal
267
+ */
268
+ _value;
269
+ /**
270
+ * @internal
271
+ */
272
+ _onChanges;
273
+ constructor(_value, _onChanges) {
274
+ this._value = _value;
275
+ this._onChanges = _onChanges;
276
+ }
277
+ /**
278
+ * If new value and old value are both nodes, the old one will be replaced in the DOM
279
+ */
280
+ get value() {
281
+ return this._value;
282
+ }
283
+ set value(newValue) {
284
+ if ($is(newValue, this._value)) {
285
+ return;
286
+ }
287
+ const oldValue = this._value;
288
+ $replaceNode(oldValue, newValue);
289
+ this._value = newValue;
290
+ for (let i = 0; i < this._onChanges.length; i++) {
291
+ this._onChanges[i](newValue, oldValue);
292
+ }
293
+ }
294
+ /**
295
+ * Register a callback when the value changes
296
+ * @param callback (newValue, oldValue) => xxx
297
+ */
298
+ addOnChange(callback) {
299
+ if (typeof callback !== 'function') {
300
+ throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
301
+ }
302
+ this._onChanges.push(callback);
303
+ }
304
+ removeOnChange(callback) {
305
+ for (let i = this._onChanges.length - 1; i >= 0; i--) {
306
+ if (this._onChanges[i] === callback) {
307
+ this._onChanges.splice(i, 1);
308
+ return true;
309
+ }
310
+ }
311
+ return false;
312
+ }
313
+ }
314
+ /**
315
+ * Reference to the created HTML element.
316
+ * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
317
+ * - can alse be used to store normal values, but it is not reactive.
318
+ * - if the value is already a `KTRef`, it will be returned **directly**.
319
+ * @param value mostly an HTMLElement
320
+ */
321
+ function ref(value, onChange) {
322
+ return new KTRef(value, []);
323
+ }
324
+
273
325
  function applyKModel(element, valueRef) {
274
- if (!isKTRef(valueRef)) {
326
+ if (!isKT(valueRef)) {
275
327
  console.warn('[kt.js warn] k-model value must be a KTRef.');
276
328
  return;
277
329
  }
@@ -298,9 +350,6 @@ const htmlCreator = (tag) => document.createElement(tag);
298
350
  const svgCreator = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
299
351
  const mathMLCreator = (tag) => document.createElementNS('http://www.w3.org/1998/Math/MathML', tag);
300
352
  let creator = htmlCreator;
301
- // # consts
302
- const SVG_ATTR_FLAG = '__kt_svg__';
303
- const MATHML_ATTR_FLAG = '__kt_mathml__';
304
353
  /**
305
354
  * Create an enhanced HTMLElement.
306
355
  * - Only supports HTMLElements, **NOT** SVGElements or other Elements.
@@ -311,7 +360,7 @@ const MATHML_ATTR_FLAG = '__kt_mathml__';
311
360
  * ## About
312
361
  * @package @ktjs/core
313
362
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
314
- * @version 0.22.5 (Last Update: 2026.02.03 09:07:21.411)
363
+ * @version 0.24.0 (Last Update: 2026.02.05 12:08:54.164)
315
364
  * @license MIT
316
365
  * @link https://github.com/baendlorel/kt.js
317
366
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -341,13 +390,7 @@ const h = (tag, attr, content) => {
341
390
  applyAttr(element, attr);
342
391
  applyContent(element, content);
343
392
  if (typeof attr === 'object' && attr !== null && 'k-model' in attr) {
344
- const kmodel = attr['k-model'];
345
- if (isKTRef(kmodel)) {
346
- applyKModel(element, kmodel);
347
- }
348
- else {
349
- throw new Error('[kt.js error] k-model value must be a KTRef.');
350
- }
393
+ applyKModel(element, attr['k-model']);
351
394
  }
352
395
  return element;
353
396
  };
@@ -367,20 +410,23 @@ const placeholder = () => document.createComment('k-if');
367
410
  * @param props properties/attributes
368
411
  */
369
412
  function jsx(tag, props) {
370
- const maybeDummyRef = isKTRef(props.ref) ? props.ref : dummyRef;
413
+ if (isComputed(props.ref)) {
414
+ throw new Error('[kt.js error] Cannot assign a computed value to an element.');
415
+ }
416
+ const maybeDummyRef = isRef(props.ref) ? props.ref : dummyRef;
371
417
  let el;
372
418
  if ('k-if' in props) {
373
419
  const kif = props['k-if'];
374
420
  let condition = kif; // assume boolean by default
375
421
  // Handle reactive k-if
376
- if (isKTRef(kif)) {
422
+ if (isKT(kif)) {
377
423
  kif.addOnChange((newValue, oldValue) => {
378
424
  if (newValue === oldValue) {
379
425
  return;
380
426
  }
381
427
  const oldEl = el;
382
428
  el = newValue ? create(tag, props) : placeholder();
383
- oldEl.replaceWith(el);
429
+ $replaceNode(oldEl, el);
384
430
  maybeDummyRef.value = el;
385
431
  });
386
432
  condition = kif.value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/core",
3
- "version": "0.22.5",
3
+ "version": "0.24.0",
4
4
  "description": "Core functionality for kt.js - DOM manipulation utilities with JSX/TSX support",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",
@@ -44,7 +44,7 @@
44
44
  "directory": "packages/core"
45
45
  },
46
46
  "dependencies": {
47
- "@ktjs/shared": "0.22.2"
47
+ "@ktjs/shared": "0.22.6"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "rollup -c rollup.config.mjs",