@ktjs/core 0.20.3 → 0.22.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.
@@ -23,6 +23,14 @@ declare class KTRef<T> {
23
23
  removeOnChange(callback: RefChangeHandler<T>): boolean;
24
24
  }
25
25
 
26
+ type HTML<T extends (HTMLTag | SVGTag | MathMLTag) & otherstring> = T extends SVGTag
27
+ ? SVGElementTagNameMap[T]
28
+ : T extends HTMLTag
29
+ ? HTMLElementTagNameMap[T]
30
+ : T extends MathMLTag
31
+ ? MathMLElementTagNameMap[T]
32
+ : HTMLElement;
33
+
26
34
  type SingleContent = KTRef<any> | HTMLElement | Element | Node | string | number | boolean | null | undefined;
27
35
  type KTAvailableContent = SingleContent | KTAvailableContent[];
28
36
  type KTRawContent = KTAvailableContent | Promise<KTAvailableContent>;
@@ -36,8 +44,18 @@ interface KTBaseAttribute {
36
44
 
37
45
  // # kt-specific attributes
38
46
  ref?: KTRef<JSX.Element>;
47
+
48
+ /**
49
+ * If a `KTRef` is bound, it will be reactive; otherwise, it will be static.
50
+ */
39
51
  'k-if'?: any;
40
52
 
53
+ /**
54
+ * Register two-way data binding between an input element and a KTRef.
55
+ * - Default to regist `input` event and `value` property(`checked` for checkboxes and radios).
56
+ */
57
+ 'k-model'?: KTRef<any>;
58
+
41
59
  // # normal HTML attributes
42
60
  id?: string;
43
61
  class?: string;
@@ -97,7 +115,6 @@ type KTPrefixedEventHandlers = {
97
115
 
98
116
  type KTAttribute = KTBaseAttribute & KTPrefixedEventHandlers;
99
117
 
100
- type HTML<T extends (HTMLTag | SVGTag | MathMLTag) & otherstring> = T extends SVGTag ? SVGElementTagNameMap[T] : T extends HTMLTag ? HTMLElementTagNameMap[T] : T extends MathMLTag ? MathMLElementTagNameMap[T] : HTMLElement;
101
118
  /**
102
119
  * Create an enhanced HTMLElement.
103
120
  * - Only supports HTMLElements, **NOT** SVGElements or other Elements.
@@ -108,7 +125,7 @@ type HTML<T extends (HTMLTag | SVGTag | MathMLTag) & otherstring> = T extends SV
108
125
  * ## About
109
126
  * @package @ktjs/core
110
127
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
111
- * @version 0.20.3 (Last Update: 2026.02.01 18:38:02.198)
128
+ * @version 0.22.0 (Last Update: 2026.02.02 17:11:04.703)
112
129
  * @license MIT
113
130
  * @link https://github.com/baendlorel/kt.js
114
131
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -6,9 +6,6 @@ const $isThenable = (o) => typeof o === 'object' && o !== null && typeof o.then
6
6
  const $throw = (message) => {
7
7
  throw new Error('@ktjs/shared: ' + message);
8
8
  };
9
-
10
- // DOM manipulation utilities
11
- // # dom natives
12
9
  /**
13
10
  * & Remove `bind` because it is shockingly slower than wrapper
14
11
  * & `window.document` is safe because it is not configurable and its setter is undefined
@@ -48,7 +45,73 @@ const { get: $buttonDisabledGetter, set: $buttonDisabledSetter } = Object.getOwn
48
45
 
49
46
  // Shared utilities and cached native methods for kt.js framework
50
47
  // Re-export all utilities
51
- Object.defineProperty(window, '@ktjs/shared', { value: '0.20.0' });
48
+ Object.defineProperty(window, '@ktjs/shared', { value: '0.20.2' });
49
+
50
+ class KTRef {
51
+ /**
52
+ * Indicates that this is a KTRef instance
53
+ */
54
+ isKT = true;
55
+ /**
56
+ * @internal
57
+ */
58
+ _value;
59
+ /**
60
+ * @internal
61
+ */
62
+ _onChanges;
63
+ constructor(_value, _onChanges) {
64
+ this._value = _value;
65
+ this._onChanges = _onChanges;
66
+ }
67
+ /**
68
+ * If new value and old value are both nodes, the old one will be replaced in the DOM
69
+ */
70
+ get value() {
71
+ return this._value;
72
+ }
73
+ set value(newValue) {
74
+ if (newValue === this._value) {
75
+ return;
76
+ }
77
+ // replace the old node with the new one in the DOM if both are nodes
78
+ if (this._value instanceof Node && newValue instanceof Node) {
79
+ if (newValue.contains(this._value)) {
80
+ this._value.remove();
81
+ }
82
+ this._value.replaceWith(newValue);
83
+ }
84
+ const oldValue = this._value;
85
+ this._value = newValue;
86
+ for (let i = 0; i < this._onChanges.length; i++) {
87
+ this._onChanges[i](newValue, oldValue);
88
+ }
89
+ }
90
+ addOnChange(callback) {
91
+ this._onChanges.push(callback);
92
+ }
93
+ removeOnChange(callback) {
94
+ for (let i = this._onChanges.length - 1; i >= 0; i--) {
95
+ if (this._onChanges[i] === callback) {
96
+ this._onChanges.splice(i, 1);
97
+ return true;
98
+ }
99
+ }
100
+ return false;
101
+ }
102
+ }
103
+ const isKTRef = (obj) => {
104
+ return typeof obj === 'object' && obj !== null && obj.isKT === true;
105
+ };
106
+ /**
107
+ * Reference to the created HTML element.
108
+ * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
109
+ * - can alse be used to store normal values, but it is not reactive.
110
+ * @param value mostly an HTMLElement
111
+ */
112
+ function ref(value, onChange) {
113
+ return new KTRef(value, []);
114
+ }
52
115
 
53
116
  const booleanHandler = (element, key, value) => {
54
117
  if (key in element) {
@@ -114,6 +177,7 @@ function attrIsObject(element, attr) {
114
177
  key === 'style' ||
115
178
  key === 'children' ||
116
179
  key === 'k-if' ||
180
+ key.startsWith('k-model') ||
117
181
  key === 'ref') {
118
182
  continue;
119
183
  }
@@ -191,7 +255,29 @@ function applyContent(element, content) {
191
255
  }
192
256
  }
193
257
 
194
- document.createElement('div');
258
+ function register(element, valueRef, propName, eventName) {
259
+ element[propName] = valueRef.value; // initialize
260
+ valueRef.addOnChange((newValue) => (element[propName] = newValue));
261
+ element.addEventListener(eventName, () => (valueRef.value = element[propName]));
262
+ }
263
+ function applyModel(element, valueRef) {
264
+ if (element instanceof HTMLInputElement) {
265
+ if (element.type === 'radio' || element.type === 'checkbox') {
266
+ register(element, valueRef, 'checked', 'change');
267
+ }
268
+ else {
269
+ register(element, valueRef, 'value', 'input');
270
+ }
271
+ }
272
+ else if (element instanceof HTMLSelectElement) {
273
+ register(element, valueRef, 'value', 'change');
274
+ }
275
+ else if (element instanceof HTMLTextAreaElement) {
276
+ register(element, valueRef, 'value', 'input');
277
+ }
278
+ console.warn('[kt.js warn] not supported element for k-model:', element.tagName);
279
+ }
280
+
195
281
  const htmlCreator = (tag) => document.createElement(tag);
196
282
  const svgCreator = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
197
283
  const mathMLCreator = (tag) => document.createElementNS('http://www.w3.org/1998/Math/MathML', tag);
@@ -209,7 +295,7 @@ const MATHML_ATTR_FLAG = '__kt_mathml__';
209
295
  * ## About
210
296
  * @package @ktjs/core
211
297
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
212
- * @version 0.20.3 (Last Update: 2026.02.01 18:38:02.198)
298
+ * @version 0.22.0 (Last Update: 2026.02.02 17:11:04.703)
213
299
  * @license MIT
214
300
  * @link https://github.com/baendlorel/kt.js
215
301
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -238,101 +324,60 @@ const h = (tag, attr, content) => {
238
324
  // * Handle content
239
325
  applyAttr(element, attr);
240
326
  applyContent(element, content);
241
- // if (tag === 'svg') {
242
- // tempWrapper.innerHTML = element.outerHTML;
243
- // return tempWrapper.firstChild as HTML<T>;
244
- // }
245
- // if (tag === 'math') {
246
- // tempWrapper.innerHTML = element.outerHTML;
247
- // return tempWrapper.firstChild as HTML<T>;
248
- // }
249
- return element;
250
- };
251
-
252
- class KTRef {
253
- /**
254
- * Indicates that this is a KTRef instance
255
- */
256
- isKT = true;
257
- /**
258
- * @internal
259
- */
260
- _value;
261
- /**
262
- * @internal
263
- */
264
- _onChanges;
265
- constructor(_value, _onChanges) {
266
- this._value = _value;
267
- this._onChanges = _onChanges;
268
- }
269
- /**
270
- * If new value and old value are both nodes, the old one will be replaced in the DOM
271
- */
272
- get value() {
273
- return this._value;
274
- }
275
- set value(newValue) {
276
- if (newValue === this._value) {
277
- return;
327
+ if (typeof attr === 'object' && attr !== null && 'k-model' in attr) {
328
+ const kmodel = attr['k-model'];
329
+ if (isKTRef(kmodel)) {
330
+ applyModel(element, kmodel);
278
331
  }
279
- // replace the old node with the new one in the DOM if both are nodes
280
- if (this._value instanceof Node && newValue instanceof Node) {
281
- if (newValue.contains(this._value)) {
282
- this._value.remove();
283
- }
284
- this._value.replaceWith(newValue);
285
- }
286
- const oldValue = this._value;
287
- this._value = newValue;
288
- for (let i = 0; i < this._onChanges.length; i++) {
289
- this._onChanges[i](newValue, oldValue);
290
- }
291
- }
292
- addOnChange(callback) {
293
- this._onChanges.push(callback);
294
- }
295
- removeOnChange(callback) {
296
- for (let i = this._onChanges.length - 1; i >= 0; i--) {
297
- if (this._onChanges[i] === callback) {
298
- this._onChanges.splice(i, 1);
299
- return true;
300
- }
332
+ else {
333
+ $throw('k-model value must be a KTRef.');
301
334
  }
302
- return false;
303
335
  }
304
- }
305
- /**
306
- * Reference to the created HTML element.
307
- * - can alse be used to store normal values, but it is not reactive.
308
- * @param value mostly an HTMLElement
309
- */
310
- function ref(value, onChange) {
311
- return new KTRef(value, []);
312
- }
336
+ return element;
337
+ };
313
338
 
314
339
  const dummyRef = { value: null };
340
+ const create = (tag, props) => {
341
+ if (typeof tag === 'function') {
342
+ return tag(props);
343
+ }
344
+ else {
345
+ return h(tag, props, props.children);
346
+ }
347
+ };
348
+ const placeholder = () => document.createComment('k-if');
315
349
  /**
316
350
  * @param tag html tag or function component
317
351
  * @param props properties/attributes
318
352
  */
319
353
  function jsx(tag, props) {
320
- const ref = props.ref?.isKT ? props.ref : dummyRef;
354
+ const maybeDummyRef = isKTRef(props.ref) ? props.ref : dummyRef;
321
355
  let el;
322
- if ('k-if' in props && !props['k-if']) {
323
- // & make comment placeholder in case that ref might be redrawn later
324
- el = document.createComment('k-if');
325
- ref.value = el;
326
- return el;
327
- }
328
- // Handle function components
329
- if (typeof tag === 'function') {
330
- el = tag(props);
331
- }
332
- else {
333
- el = h(tag, props, props.children);
356
+ if ('k-if' in props) {
357
+ const kif = props['k-if'];
358
+ let condition = kif; // assume boolean by default
359
+ // Handle reactive k-if
360
+ if (isKTRef(kif)) {
361
+ kif.addOnChange((newValue, oldValue) => {
362
+ if (newValue === oldValue) {
363
+ return;
364
+ }
365
+ const oldEl = el;
366
+ el = newValue ? create(tag, props) : placeholder();
367
+ oldEl.replaceWith(el);
368
+ maybeDummyRef.value = el;
369
+ });
370
+ condition = kif.value;
371
+ }
372
+ if (!condition) {
373
+ // & make comment placeholder in case that ref might be redrawn later
374
+ el = placeholder();
375
+ maybeDummyRef.value = el;
376
+ return el;
377
+ }
334
378
  }
335
- ref.value = el;
379
+ el = create(tag, props);
380
+ maybeDummyRef.value = el;
336
381
  return el;
337
382
  }
338
383
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/core",
3
- "version": "0.20.3",
3
+ "version": "0.22.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.20.0"
47
+ "@ktjs/shared": "0.20.2"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "rollup -c rollup.config.mjs",