@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.
package/dist/index.mjs CHANGED
@@ -1,10 +1,35 @@
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;
14
+ const $is = Object.is;
3
15
  const $entries = Object.entries;
4
- const $isThenable = (o) => typeof o === 'object' && o !== null && typeof o.then === 'function';
16
+ const $random = Math.random;
17
+ const $isThenable = (o) => typeof o?.then === 'function';
5
18
 
6
19
  // DOM manipulation utilities
7
20
  // # dom natives
21
+ const $isNode = (x) => x?.nodeType > 0;
22
+ /**
23
+ * Safe replace `oldNode` With `newNode`
24
+ */
25
+ const $replaceNode = (oldNode, newNode) => {
26
+ if ($isNode(oldNode) && $isNode(newNode)) {
27
+ if (newNode.contains(oldNode)) {
28
+ newNode.remove();
29
+ }
30
+ oldNode.replaceWith(newNode);
31
+ }
32
+ };
8
33
  /**
9
34
  * & Remove `bind` because it is shockingly slower than wrapper
10
35
  * & `window.document` is safe because it is not configurable and its setter is undefined
@@ -50,129 +75,21 @@ const applyModel = (element, valueRef, propName, eventName) => {
50
75
  element.addEventListener(eventName, () => (valueRef.value = element[propName]));
51
76
  };
52
77
 
78
+ if (typeof Symbol === 'undefined') {
79
+ window.Symbol = function Symbol(description) {
80
+ return `@@SYMBOL_${description || ''}_${$random().toString(36).slice(2)}`;
81
+ };
82
+ }
83
+
53
84
  // Shared utilities and cached native methods for kt.js framework
54
- // Re-export all utilities
55
- Object.defineProperty(window, '__ktjs__', { value: '0.22.2' });
85
+ Object.defineProperty(window, '__ktjs__', { value: '0.22.6' });
56
86
 
57
- class KTRef {
58
- /**
59
- * Indicates that this is a KTRef instance
60
- */
61
- isKT = true;
62
- /**
63
- * @internal
64
- */
65
- _value;
66
- /**
67
- * @internal
68
- */
69
- _onChanges;
70
- constructor(_value, _onChanges) {
71
- this._value = _value;
72
- this._onChanges = _onChanges;
73
- }
74
- /**
75
- * If new value and old value are both nodes, the old one will be replaced in the DOM
76
- */
77
- get value() {
78
- return this._value;
79
- }
80
- set value(newValue) {
81
- if (newValue === this._value) {
82
- return;
83
- }
84
- // replace the old node with the new one in the DOM if both are nodes
85
- if (this._value instanceof Node && newValue instanceof Node) {
86
- if (newValue.contains(this._value)) {
87
- this._value.remove();
88
- }
89
- this._value.replaceWith(newValue);
90
- }
91
- const oldValue = this._value;
92
- this._value = newValue;
93
- for (let i = 0; i < this._onChanges.length; i++) {
94
- this._onChanges[i](newValue, oldValue);
95
- }
96
- }
97
- /**
98
- * Register a callback when the value changes
99
- * @param callback (newValue, oldValue) => xxx
100
- */
101
- addOnChange(callback) {
102
- if (typeof callback !== 'function') {
103
- throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
104
- }
105
- this._onChanges.push(callback);
106
- }
107
- removeOnChange(callback) {
108
- for (let i = this._onChanges.length - 1; i >= 0; i--) {
109
- if (this._onChanges[i] === callback) {
110
- this._onChanges.splice(i, 1);
111
- return true;
112
- }
113
- }
114
- return false;
115
- }
116
- }
117
- const isKTRef = (obj) => obj?.isKT === true;
118
- /**
119
- * Reference to the created HTML element.
120
- * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
121
- * - can alse be used to store normal values, but it is not reactive.
122
- * - if the value is already a `KTRef`, it will be returned **directly**.
123
- * @param value mostly an HTMLElement
124
- */
125
- function ref(value, onChange) {
126
- if (isKTRef(value)) {
127
- if (onChange) {
128
- value.addOnChange(onChange);
129
- }
130
- return value;
131
- }
132
- return new KTRef(value, onChange ? [onChange] : []);
133
- }
134
- function deref(value) {
135
- return isKTRef(value) ? value.value : value;
136
- }
137
- function kcollect() {
138
- const newObj = {};
139
- const entries = $entries(this);
140
- for (let i = 0; i < entries.length; i++) {
141
- const key = entries[i][0];
142
- if (key === 'kcollect') {
143
- continue;
144
- }
145
- newObj[key] = entries[i][1].value;
146
- }
147
- return newObj;
148
- }
149
- /**
150
- * Make all first-level properties of the object a `KTRef`.
151
- * - `obj.a.b` is not reactive
152
- */
153
- const surfaceRef = (obj) => {
154
- const entries = $entries(obj);
155
- const newObj = { kcollect };
156
- for (let i = 0; i < entries.length; i++) {
157
- newObj[entries[i][0]] = ref(entries[i][1]);
158
- }
159
- return newObj;
160
- };
161
- // # asserts
162
- /**
163
- * Assert k-model to be a ref object
164
- */
165
- const $modelOrRef = (props, defaultValue) => {
166
- // & props is an object. Won't use it in any other place
167
- if ('k-model' in props) {
168
- const kmodel = props['k-model'];
169
- if (!kmodel?.isKT) {
170
- throw new Error(`[kt.js error] k-model data must be a KTRef object, please use 'ref(...)' to wrap it.`);
171
- }
172
- return kmodel;
173
- }
174
- return ref(defaultValue);
175
- };
87
+ // export const KT_TYPE_REF = 1 as const;
88
+ // export const KT_TYPE_COMPUTED = 2 as const;
89
+ // export type KTReactiveTypeEnum = typeof KT_TYPE_REF | typeof KT_TYPE_COMPUTED;
90
+ const isKT = (obj) => obj?.isKT;
91
+ const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
92
+ const isComputed = (obj) => obj?.ktType === 2 /* KTReactiveType.COMPUTED */;
176
93
 
177
94
  const booleanHandler = (element, key, value) => {
178
95
  if (key in element) {
@@ -232,14 +149,25 @@ function attrIsObject(element, attr) {
232
149
  }
233
150
  }
234
151
  }
152
+ if ('k-html' in attr) {
153
+ const html = attr['k-html'];
154
+ if (isKT(html)) {
155
+ element.innerHTML = html.value;
156
+ html.addOnChange((v) => (element.innerHTML = v));
157
+ }
158
+ else {
159
+ element.innerHTML = html;
160
+ }
161
+ }
235
162
  for (const key in attr) {
236
- if (key === 'class' ||
163
+ if (key === 'k-if' ||
164
+ key === 'k-model' ||
165
+ key === 'ref' ||
166
+ key === 'class' ||
237
167
  key === 'className' ||
238
168
  key === 'style' ||
239
169
  key === 'children' ||
240
- key === 'k-if' ||
241
- key.startsWith('k-model') ||
242
- key === 'ref') {
170
+ key === 'k-html') {
243
171
  continue;
244
172
  }
245
173
  const o = attr[key];
@@ -249,6 +177,7 @@ function attrIsObject(element, attr) {
249
177
  }
250
178
  // normal attributes
251
179
  else {
180
+ // todo 这里也可以绑定ref的
252
181
  (handlers[key] || defaultHandler)(element, key, o);
253
182
  }
254
183
  }
@@ -265,13 +194,25 @@ function applyAttr(element, attr) {
265
194
  }
266
195
  }
267
196
 
197
+ const assureNode = (o) => ($isNode(o) ? o : document.createTextNode(o));
268
198
  function apdSingle(element, c) {
269
199
  // & JSX should ignore false, undefined, and null
270
200
  if (c === false || c === undefined || c === null) {
271
201
  return;
272
202
  }
273
- if (typeof c === 'object' && c !== null && 'isKT' in c) {
274
- $append.call(element, c.value);
203
+ if (isKT(c)) {
204
+ let node = assureNode(c.value);
205
+ $append.call(element, node);
206
+ c.addOnChange((newValue, oldValue) => {
207
+ if ($isNode(newValue) && $isNode(oldValue)) {
208
+ // & this case is handled automically in `class KTRef`
209
+ // todo 2 cases might be able to merge into 1
210
+ return;
211
+ }
212
+ const oldNode = node;
213
+ node = assureNode(newValue);
214
+ oldNode.replaceWith(node);
215
+ });
275
216
  }
276
217
  else {
277
218
  $append.call(element, c);
@@ -316,8 +257,218 @@ function applyContent(element, content) {
316
257
  }
317
258
  }
318
259
 
260
+ class KTRef {
261
+ /**
262
+ * Indicates that this is a KTRef instance
263
+ */
264
+ isKT = true;
265
+ ktType = 1 /* KTReactiveType.REF */;
266
+ /**
267
+ * @internal
268
+ */
269
+ _value;
270
+ /**
271
+ * @internal
272
+ */
273
+ _onChanges;
274
+ constructor(_value, _onChanges) {
275
+ this._value = _value;
276
+ this._onChanges = _onChanges;
277
+ }
278
+ /**
279
+ * If new value and old value are both nodes, the old one will be replaced in the DOM
280
+ */
281
+ get value() {
282
+ return this._value;
283
+ }
284
+ set value(newValue) {
285
+ if ($is(newValue, this._value)) {
286
+ return;
287
+ }
288
+ const oldValue = this._value;
289
+ $replaceNode(oldValue, newValue);
290
+ this._value = newValue;
291
+ for (let i = 0; i < this._onChanges.length; i++) {
292
+ this._onChanges[i](newValue, oldValue);
293
+ }
294
+ }
295
+ /**
296
+ * Register a callback when the value changes
297
+ * @param callback (newValue, oldValue) => xxx
298
+ */
299
+ addOnChange(callback) {
300
+ if (typeof callback !== 'function') {
301
+ throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
302
+ }
303
+ this._onChanges.push(callback);
304
+ }
305
+ removeOnChange(callback) {
306
+ for (let i = this._onChanges.length - 1; i >= 0; i--) {
307
+ if (this._onChanges[i] === callback) {
308
+ this._onChanges.splice(i, 1);
309
+ return true;
310
+ }
311
+ }
312
+ return false;
313
+ }
314
+ }
315
+ /**
316
+ * Reference to the created HTML element.
317
+ * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
318
+ * - can alse be used to store normal values, but it is not reactive.
319
+ * - if the value is already a `KTRef`, it will be returned **directly**.
320
+ * @param value mostly an HTMLElement
321
+ */
322
+ function ref(value, onChange) {
323
+ return new KTRef(value, onChange ? [onChange] : []);
324
+ }
325
+ /**
326
+ * Convert a value to `KTRef`.
327
+ * - Returns the original value if it is already a `KTRef`.
328
+ * - Throws error if the value is a `KTComputed`.
329
+ * - Otherwise wraps the value with `ref()`.
330
+ * @param o value to convert
331
+ */
332
+ const toRef = (o) => {
333
+ if (isRef(o)) {
334
+ return o;
335
+ }
336
+ else if (isComputed(o)) {
337
+ throw new Error('[kt.js error] Computed values cannot be used as KTRef.');
338
+ }
339
+ else {
340
+ return ref(o);
341
+ }
342
+ };
343
+ function deref(value) {
344
+ return isKT(value) ? value.value : value;
345
+ }
346
+ function kcollect() {
347
+ const newObj = {};
348
+ const entries = $entries(this);
349
+ for (let i = 0; i < entries.length; i++) {
350
+ const key = entries[i][0];
351
+ if (key === 'kcollect') {
352
+ continue;
353
+ }
354
+ newObj[key] = entries[i][1].value;
355
+ }
356
+ return newObj;
357
+ }
358
+ /**
359
+ * Make all first-level properties of the object a `KTRef`.
360
+ * - `obj.a.b` is not reactive
361
+ */
362
+ const surfaceRef = (obj) => {
363
+ const entries = $entries(obj);
364
+ const newObj = { kcollect };
365
+ for (let i = 0; i < entries.length; i++) {
366
+ newObj[entries[i][0]] = ref(entries[i][1]);
367
+ }
368
+ return newObj;
369
+ };
370
+ // # asserts
371
+ /**
372
+ * Assert k-model to be a ref object
373
+ */
374
+ const $modelOrRef = (props, defaultValue) => {
375
+ // & props is an object. Won't use it in any other place
376
+ if ('k-model' in props) {
377
+ const kmodel = props['k-model'];
378
+ if (!kmodel?.isKT) {
379
+ throw new Error(`[kt.js error] k-model data must be a KTRef object, please use 'ref(...)' to wrap it.`);
380
+ }
381
+ return kmodel;
382
+ }
383
+ return ref(defaultValue);
384
+ };
385
+
386
+ class KTComputed {
387
+ /**
388
+ * Indicates that this is a KTRef instance
389
+ */
390
+ isKT = true;
391
+ ktType = 2 /* KTReactiveType.COMPUTED */;
392
+ /**
393
+ * @internal
394
+ */
395
+ _calculator;
396
+ /**
397
+ * @internal
398
+ */
399
+ _value;
400
+ /**
401
+ * @internal
402
+ */
403
+ _onChanges = [];
404
+ _subscribe(reactives) {
405
+ for (let i = 0; i < reactives.length; i++) {
406
+ const reactive = reactives[i];
407
+ reactive.addOnChange(() => {
408
+ const oldValue = this._value;
409
+ this._value = this._calculator();
410
+ if (oldValue === this._value) {
411
+ return;
412
+ }
413
+ $replaceNode(oldValue, this._value);
414
+ for (let i = 0; i < this._onChanges.length; i++) {
415
+ this._onChanges[i](this._value, oldValue);
416
+ }
417
+ });
418
+ }
419
+ }
420
+ constructor(_calculator, reactives) {
421
+ this._calculator = _calculator;
422
+ this._value = _calculator();
423
+ this._subscribe(reactives);
424
+ }
425
+ /**
426
+ * If new value and old value are both nodes, the old one will be replaced in the DOM
427
+ */
428
+ get value() {
429
+ return this._value;
430
+ }
431
+ set value(_newValue) {
432
+ throw new Error('[kt.js error] KTComputed: cannot set value of a computed value');
433
+ }
434
+ /**
435
+ * Register a callback when the value changes
436
+ * @param callback (newValue, oldValue) => xxx
437
+ */
438
+ addOnChange(callback) {
439
+ if (typeof callback !== 'function') {
440
+ throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
441
+ }
442
+ this._onChanges.push(callback);
443
+ }
444
+ /**
445
+ * Unregister a callback
446
+ * @param callback (newValue, oldValue) => xxx
447
+ */
448
+ removeOnChange(callback) {
449
+ for (let i = this._onChanges.length - 1; i >= 0; i--) {
450
+ if (this._onChanges[i] === callback) {
451
+ this._onChanges.splice(i, 1);
452
+ return true;
453
+ }
454
+ }
455
+ return false;
456
+ }
457
+ }
458
+ /**
459
+ * Create a reactive computed value
460
+ * @param computeFn
461
+ * @param reactives refs and computeds that this computed depends on
462
+ */
463
+ function computed(computeFn, reactives) {
464
+ if (reactives.some((v) => !isKT(v))) {
465
+ throw new Error('[kt.js error] computed: all reactives must be KTRef or KTComputed instances');
466
+ }
467
+ return new KTComputed(computeFn, reactives);
468
+ }
469
+
319
470
  function applyKModel(element, valueRef) {
320
- if (!isKTRef(valueRef)) {
471
+ if (!isKT(valueRef)) {
321
472
  console.warn('[kt.js warn] k-model value must be a KTRef.');
322
473
  return;
323
474
  }
@@ -344,9 +495,6 @@ const htmlCreator = (tag) => document.createElement(tag);
344
495
  const svgCreator = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
345
496
  const mathMLCreator = (tag) => document.createElementNS('http://www.w3.org/1998/Math/MathML', tag);
346
497
  let creator = htmlCreator;
347
- // # consts
348
- const SVG_ATTR_FLAG = '__kt_svg__';
349
- const MATHML_ATTR_FLAG = '__kt_mathml__';
350
498
  /**
351
499
  * Create an enhanced HTMLElement.
352
500
  * - Only supports HTMLElements, **NOT** SVGElements or other Elements.
@@ -357,7 +505,7 @@ const MATHML_ATTR_FLAG = '__kt_mathml__';
357
505
  * ## About
358
506
  * @package @ktjs/core
359
507
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
360
- * @version 0.22.5 (Last Update: 2026.02.03 09:07:21.411)
508
+ * @version 0.24.0 (Last Update: 2026.02.05 12:08:54.164)
361
509
  * @license MIT
362
510
  * @link https://github.com/baendlorel/kt.js
363
511
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -387,13 +535,7 @@ const h = (tag, attr, content) => {
387
535
  applyAttr(element, attr);
388
536
  applyContent(element, content);
389
537
  if (typeof attr === 'object' && attr !== null && 'k-model' in attr) {
390
- const kmodel = attr['k-model'];
391
- if (isKTRef(kmodel)) {
392
- applyKModel(element, kmodel);
393
- }
394
- else {
395
- throw new Error('[kt.js error] k-model value must be a KTRef.');
396
- }
538
+ applyKModel(element, attr['k-model']);
397
539
  }
398
540
  return element;
399
541
  };
@@ -413,20 +555,23 @@ const placeholder = () => document.createComment('k-if');
413
555
  * @param props properties/attributes
414
556
  */
415
557
  function jsx(tag, props) {
416
- const maybeDummyRef = isKTRef(props.ref) ? props.ref : dummyRef;
558
+ if (isComputed(props.ref)) {
559
+ throw new Error('[kt.js error] Cannot assign a computed value to an element.');
560
+ }
561
+ const maybeDummyRef = isRef(props.ref) ? props.ref : dummyRef;
417
562
  let el;
418
563
  if ('k-if' in props) {
419
564
  const kif = props['k-if'];
420
565
  let condition = kif; // assume boolean by default
421
566
  // Handle reactive k-if
422
- if (isKTRef(kif)) {
567
+ if (isKT(kif)) {
423
568
  kif.addOnChange((newValue, oldValue) => {
424
569
  if (newValue === oldValue) {
425
570
  return;
426
571
  }
427
572
  const oldEl = el;
428
573
  el = newValue ? create(tag, props) : placeholder();
429
- oldEl.replaceWith(el);
574
+ $replaceNode(oldEl, el);
430
575
  maybeDummyRef.value = el;
431
576
  });
432
577
  condition = kif.value;
@@ -683,7 +828,7 @@ function KTFor(props) {
683
828
  return anchor;
684
829
  };
685
830
  // Set ref if provided
686
- if (props.ref?.isKT) {
831
+ if (isRef(props.ref)) {
687
832
  props.ref.value = anchor;
688
833
  }
689
834
  return anchor;
@@ -731,4 +876,4 @@ function getSequence(arr) {
731
876
  return result;
732
877
  }
733
878
 
734
- export { $modelOrRef, Fragment, KTAsync, KTFor, KTRef, h as createElement, createRedrawable, deref, h, isKTRef, jsx, jsxDEV, jsxs, ref, surfaceRef };
879
+ export { $modelOrRef, Fragment, KTAsync, KTComputed, KTFor, KTRef, computed, h as createElement, createRedrawable, deref, h, isComputed, isKT, isRef, jsx, jsxDEV, jsxs, ref, surfaceRef, toRef };