kt.js 0.9.0 → 0.10.1
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/README.md +76 -30
- package/dist/jsx-runtime.mjs +36 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -45,13 +45,17 @@ KT.js follows one rule: **full control of DOM and avoid unnecessary repainting**
|
|
|
45
45
|
- **Tiny Bundle Size**: Minimal runtime overhead with aggressive tree-shaking
|
|
46
46
|
- **`h` function**: Create DOM elements with a simple, flexible API
|
|
47
47
|
- Shortcut functions for all HTML elements (`div`, `span`, `button`, etc.)
|
|
48
|
-
- Event handlers with
|
|
48
|
+
- Event handlers with `on:<eventName>` syntax or function attributes
|
|
49
49
|
- Full TypeScript support with intelligent type inference
|
|
50
50
|
- **JSX/TSX Support**: Full JSX syntax support with TypeScript integration
|
|
51
51
|
- Zero virtual DOM - JSX compiles directly to `h()` function calls
|
|
52
52
|
- Full HTML element type inference (`<button>` returns `HTMLButtonElement`)
|
|
53
53
|
- Support for `@click` event handler syntax
|
|
54
54
|
- No Fragment support - KT.js doesn't have a Fragment concept
|
|
55
|
+
- **Async Components**: Built-in support for Promise-based components
|
|
56
|
+
- `KTAsync` component for handling async operations
|
|
57
|
+
- Automatic placeholder management during loading
|
|
58
|
+
- Seamless integration with JSX/TSX syntax
|
|
55
59
|
- **Client-Side Router** (separate package):
|
|
56
60
|
- Hash-based routing with dynamic parameters
|
|
57
61
|
- Navigation guards with async/sync auto-adaptation
|
|
@@ -64,7 +68,6 @@ KT.js follows one rule: **full control of DOM and avoid unnecessary repainting**
|
|
|
64
68
|
- **Full ES5 Compatibility**: Works in IE9+ and all modern browsers
|
|
65
69
|
- Transpiled to ES5 with no modern syntax
|
|
66
70
|
- Optional minimal Promise polyfill for older environments
|
|
67
|
-
- **`ktnull`**: A special value for filtering null/undefined while preserving native DOM behavior
|
|
68
71
|
- **Shared Runtime**: Efficient code sharing across packages with zero overhead
|
|
69
72
|
|
|
70
73
|
## Getting started
|
|
@@ -106,7 +109,7 @@ KT.js now has full JSX support! With the `@ktjs/jsx` package (included in the ma
|
|
|
106
109
|
{
|
|
107
110
|
"compilerOptions": {
|
|
108
111
|
"jsx": "react-jsx",
|
|
109
|
-
"jsxImportSource": "
|
|
112
|
+
"jsxImportSource": "kt.js"
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
```
|
|
@@ -122,9 +125,7 @@ function Counter() {
|
|
|
122
125
|
return (
|
|
123
126
|
<div class="counter">
|
|
124
127
|
<h1>Counter: {count}</h1>
|
|
125
|
-
<button
|
|
126
|
-
Increment
|
|
127
|
-
</button>
|
|
128
|
+
<button on:click={() => console.log('Clicked!')}>Increment</button>
|
|
128
129
|
</div>
|
|
129
130
|
);
|
|
130
131
|
}
|
|
@@ -141,8 +142,7 @@ function App() {
|
|
|
141
142
|
|
|
142
143
|
return (
|
|
143
144
|
<div>
|
|
144
|
-
<button
|
|
145
|
-
<button onclick={handleClick}>Also works</button>
|
|
145
|
+
<button on:click={handleClick}>Click me</button>
|
|
146
146
|
</div>
|
|
147
147
|
);
|
|
148
148
|
}
|
|
@@ -168,14 +168,80 @@ const div: HTMLDivElement = <div className="container" id="main" />;
|
|
|
168
168
|
- Use `@click` syntax for event handlers to avoid conflicts with existing attributes
|
|
169
169
|
- All JSX elements have proper HTML element type inference in TypeScript
|
|
170
170
|
|
|
171
|
+
### Async Components with KTAsync
|
|
172
|
+
|
|
173
|
+
KT.js provides built-in support for async components through the `KTAsync` component:
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
import { KTAsync, ref } from 'kt.js';
|
|
177
|
+
|
|
178
|
+
// Define an async component that returns a Promise<HTMLElement>
|
|
179
|
+
const AsyncUserCard = function () {
|
|
180
|
+
return fetch('/api/user')
|
|
181
|
+
.then((res) => res.json())
|
|
182
|
+
.then((user) => (
|
|
183
|
+
<div class="user-card">
|
|
184
|
+
<h2>{user.name}</h2>
|
|
185
|
+
<p>{user.email}</p>
|
|
186
|
+
</div>
|
|
187
|
+
));
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Use KTAsync to handle the async component
|
|
191
|
+
function App() {
|
|
192
|
+
return (
|
|
193
|
+
<div class="app">
|
|
194
|
+
<h1>User Profile</h1>
|
|
195
|
+
<KTAsync component={AsyncUserCard} />
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// The component starts with a placeholder comment node
|
|
201
|
+
// When the Promise resolves, it automatically replaces with the actual element
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**How KTAsync works:**
|
|
205
|
+
|
|
206
|
+
1. Creates a placeholder comment node (`ktjs-suspense-placeholder`) immediately
|
|
207
|
+
2. Calls your component function (which should return a `Promise<HTMLElement>` or `HTMLElement`)
|
|
208
|
+
3. When the Promise resolves, automatically replaces the placeholder with the resolved element
|
|
209
|
+
4. If your component returns a non-Promise value, it's used directly without async handling
|
|
210
|
+
|
|
211
|
+
**Example with dynamic updates:**
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
const DynamicContent = function () {
|
|
215
|
+
const count = ref(0);
|
|
216
|
+
const container = (
|
|
217
|
+
<div>
|
|
218
|
+
<p>Count: {count}</p>
|
|
219
|
+
<button on:click={() => count.value++}>Increment</button>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Simulate async data loading
|
|
224
|
+
return new Promise<HTMLElement>((resolve) => {
|
|
225
|
+
setTimeout(() => resolve(container), 500);
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Usage
|
|
230
|
+
const app = (
|
|
231
|
+
<div>
|
|
232
|
+
<h1>Loading async content...</h1>
|
|
233
|
+
<KTAsync component={DynamicContent} />
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
```
|
|
237
|
+
|
|
171
238
|
If you give a function in attributes, it will be treated as an event listener, and the key will be considered as the event name. `@<eventName>` will also be considered as the handler to avoid conflicts with existing attributes:
|
|
172
239
|
|
|
173
240
|
```ts
|
|
174
241
|
const button = btn(
|
|
175
242
|
{
|
|
176
|
-
click: () => alert('Clicked!'),
|
|
177
243
|
dblclick: '22',
|
|
178
|
-
'
|
|
244
|
+
'on:dblclick': function trueHandler() {
|
|
179
245
|
/* ... */
|
|
180
246
|
},
|
|
181
247
|
},
|
|
@@ -301,26 +367,6 @@ console.log(router.current?.path, router.current?.params, router.current?.query)
|
|
|
301
367
|
- **Zero Dependencies**: Fully self-contained router implementation (does not require `@ktjs/core` for runtime, only for TypeScript types)
|
|
302
368
|
- **Pure Routing**: No rendering logic - you control the DOM
|
|
303
369
|
|
|
304
|
-
## `ktnull`
|
|
305
|
-
|
|
306
|
-
`ktnull`, assigned by `Object.create(null)`, is a falsy value.
|
|
307
|
-
It is used for filtering, you can do like this:
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
import { div, ktnull } from 'kt.js';
|
|
311
|
-
const list = div('', [false ? div('', 'item 1') : ktnull, div('', 'Item 2'), undefined]);
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
Then it will create:
|
|
315
|
-
|
|
316
|
-
```html
|
|
317
|
-
<div>
|
|
318
|
-
<!-- there won't be <div>Item 1</div> -->
|
|
319
|
-
<div>Item 2</div>
|
|
320
|
-
undefined
|
|
321
|
-
</div>
|
|
322
|
-
```
|
|
323
|
-
|
|
324
370
|
## Browser Compatibility
|
|
325
371
|
|
|
326
372
|
KT.js is transpiled to ES5 and works in all modern browsers as well as legacy browsers including IE9+.
|
package/dist/jsx-runtime.mjs
CHANGED
|
@@ -10,12 +10,7 @@ const emptyPromiseHandler = () => ({});
|
|
|
10
10
|
if (typeof Promise === 'undefined') {
|
|
11
11
|
window.Promise = { resolve: emptyPromiseHandler, reject: emptyPromiseHandler };
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* This is a `falsy` value used to indicate "no node" in `h` function.
|
|
16
|
-
* - It's an object, so it's guaranteed to be unique and no need for polyfill of `symbol`.
|
|
17
|
-
*/
|
|
18
|
-
const ktnull = Object.create(null);
|
|
13
|
+
const $isThenable = (o) => typeof o === 'object' && o !== null && 'then' in o && typeof o.then === 'function';
|
|
19
14
|
|
|
20
15
|
/**
|
|
21
16
|
* & Remove `bind` because it is shockingly slower than wrapper
|
|
@@ -26,8 +21,7 @@ const originAppend = HTMLElement.prototype.append;
|
|
|
26
21
|
const $append = // for ie 9/10/11
|
|
27
22
|
typeof originAppend === 'function'
|
|
28
23
|
? function (...args) {
|
|
29
|
-
|
|
30
|
-
return originAppend.apply(this, nodes);
|
|
24
|
+
return originAppend.apply(this, args);
|
|
31
25
|
}
|
|
32
26
|
: function (...nodes) {
|
|
33
27
|
if (nodes.length < 50) {
|
|
@@ -36,7 +30,7 @@ const $append = // for ie 9/10/11
|
|
|
36
30
|
if (typeof node === 'string') {
|
|
37
31
|
$appendChild.call(this, document.createTextNode(node));
|
|
38
32
|
}
|
|
39
|
-
else
|
|
33
|
+
else {
|
|
40
34
|
$appendChild.call(this, node);
|
|
41
35
|
}
|
|
42
36
|
}
|
|
@@ -48,7 +42,7 @@ const $append = // for ie 9/10/11
|
|
|
48
42
|
if (typeof node === 'string') {
|
|
49
43
|
$appendChild.call(fragment, document.createTextNode(node));
|
|
50
44
|
}
|
|
51
|
-
else
|
|
45
|
+
else {
|
|
52
46
|
$appendChild.call(fragment, node);
|
|
53
47
|
}
|
|
54
48
|
}
|
|
@@ -155,12 +149,35 @@ function applyAttr(element, attr) {
|
|
|
155
149
|
}
|
|
156
150
|
}
|
|
157
151
|
|
|
158
|
-
function
|
|
159
|
-
if (
|
|
160
|
-
$append.call(element,
|
|
152
|
+
function apdSingle(element, c) {
|
|
153
|
+
if (typeof c === 'object' && c !== null && 'isKT' in c) {
|
|
154
|
+
$append.call(element, c.value);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
$append.call(element, c);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function apd(element, c) {
|
|
161
|
+
if ($isThenable(c)) {
|
|
162
|
+
c.then((r) => apd(element, r));
|
|
163
|
+
}
|
|
164
|
+
else if ($isArray(c)) {
|
|
165
|
+
for (let i = 0; i < c.length; i++) {
|
|
166
|
+
// & might be thenable here too
|
|
167
|
+
const ci = c[i];
|
|
168
|
+
if ($isThenable(ci)) {
|
|
169
|
+
const comment = document.createComment('ktjs-promise-placeholder');
|
|
170
|
+
$append.call(element, comment);
|
|
171
|
+
ci.then((awaited) => comment.replaceWith(awaited));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
apdSingle(element, ci);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
161
177
|
}
|
|
162
178
|
else {
|
|
163
|
-
|
|
179
|
+
// & here is thened, so must be a simple elementj
|
|
180
|
+
apdSingle(element, c);
|
|
164
181
|
}
|
|
165
182
|
}
|
|
166
183
|
function applyContent(element, content) {
|
|
@@ -184,7 +201,7 @@ function applyContent(element, content) {
|
|
|
184
201
|
* ## About
|
|
185
202
|
* @package @ktjs/core
|
|
186
203
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
187
|
-
* @version 0.
|
|
204
|
+
* @version 0.10.1 (Last Update: 2025.12.30 14:45:59.803)
|
|
188
205
|
* @license MIT
|
|
189
206
|
* @link https://github.com/baendlorel/kt.js
|
|
190
207
|
* @link https://baendlorel.github.io/ Welcome to my site!
|
|
@@ -193,7 +210,7 @@ function applyContent(element, content) {
|
|
|
193
210
|
*/
|
|
194
211
|
const h = ((tag, attr = '', content = '') => {
|
|
195
212
|
if (typeof tag !== 'string') {
|
|
196
|
-
$throw('
|
|
213
|
+
$throw('tagName must be a string.');
|
|
197
214
|
}
|
|
198
215
|
// * start creating the element
|
|
199
216
|
const element = document.createElement(tag);
|
|
@@ -237,13 +254,12 @@ function jsx(tag, props, ..._metadata) {
|
|
|
237
254
|
/**
|
|
238
255
|
* Fragment support - returns an array of children
|
|
239
256
|
* Note: kt.js doesn't have a real Fragment concept,
|
|
240
|
-
* so we return ktnull for empty fragments or flatten children
|
|
241
257
|
*/
|
|
242
258
|
function Fragment(props) {
|
|
243
259
|
window.__ktjs__.throws("kt.js doesn't have a Fragment concept");
|
|
244
260
|
// const { children } = props || {};
|
|
245
261
|
// if (!children) {
|
|
246
|
-
// return
|
|
262
|
+
// return ;
|
|
247
263
|
// }
|
|
248
264
|
// // If single child, return it directly
|
|
249
265
|
// if (!Array.isArray(children)) {
|
|
@@ -254,13 +270,11 @@ function Fragment(props) {
|
|
|
254
270
|
// const wrapper = document.createElement('div');
|
|
255
271
|
// wrapper.setAttribute('data-kt-fragment', 'true');
|
|
256
272
|
// children.forEach((child) => {
|
|
257
|
-
// if (child && child !== ktnull) {
|
|
258
273
|
// if (typeof child === 'string') {
|
|
259
274
|
// wrapper.appendChild(document.createTextNode(child));
|
|
260
275
|
// } else if (child instanceof HTMLElement) {
|
|
261
276
|
// wrapper.appendChild(child);
|
|
262
277
|
// }
|
|
263
|
-
// }
|
|
264
278
|
// });
|
|
265
279
|
// return wrapper;
|
|
266
280
|
}
|
|
@@ -268,8 +282,8 @@ function Fragment(props) {
|
|
|
268
282
|
* JSX Development runtime - same as jsx but with additional dev checks
|
|
269
283
|
*/
|
|
270
284
|
const jsxDEV = (...args) => {
|
|
271
|
-
console.log('JSX DEV called:', ...args);
|
|
272
|
-
console.log('
|
|
285
|
+
// console.log('JSX DEV called:', ...args);
|
|
286
|
+
// console.log('children', (args[1] as any)?.children);
|
|
273
287
|
return jsx(...args);
|
|
274
288
|
};
|
|
275
289
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kt.js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Kasukabe Tsumugi",
|
|
6
6
|
"email": "futami16237@gmail.com"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
],
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@ktjs/core": "0.
|
|
44
|
+
"@ktjs/core": "0.10.1",
|
|
45
45
|
"@ktjs/shortcuts": "0.7.3"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|