kt.js 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.
- package/README.md +117 -330
- package/dist/jsx-runtime.mjs +134 -89
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/kt.js) [](https://github.com/baendlorel/kt.js/blob/main/LICENSE)
|
|
4
4
|
|
|
5
|
-
[CHANGLOG✨](CHANGELOG.md)
|
|
6
|
-
|
|
7
|
-
## What's New (v0.19.x)
|
|
8
|
-
|
|
9
|
-
- Release `0.19.0` (2026-01-31): build & packaging fixes, TypeScript and lint cleanups, MUI-focused fixes (JSX handling, `jsxImportSource` set to `@ktjs/core`, radio/checkbox/TextField fixes), and repo cleanup (removed alias detection, moved shared utilities into `shared`). See the full details in the CHANGELOG.
|
|
5
|
+
[CHANGLOG✨](../../CHANGELOG.md)
|
|
10
6
|
|
|
11
7
|
> Note: This framework is still under development. APIs, type declarations, and other parts **may change frequently**. If you use it, please watch for updates in the near future. Feel free to mail me if you have any questions!
|
|
12
8
|
|
|
@@ -21,6 +17,7 @@ KT.js is now a **monorepo** containing multiple packages:
|
|
|
21
17
|
- **[kt.js](./packages/kt.js)**: Main entry package that re-exports all functionality
|
|
22
18
|
- **[@ktjs/core](./packages/core)**: Core DOM manipulation utilities and the `h` function. SX/TSX support with full TypeScript integration (included in kt.js package)
|
|
23
19
|
- **[@ktjs/router](./packages/router)**: Client-side routing with navigation guards (not included in kt.js package)
|
|
20
|
+
- **[@ktjs/mui](./packages/mui)**: Material UI components built on top of KT.js (not included in kt.js package)
|
|
24
21
|
|
|
25
22
|
You can install the full package or individual packages as needed:
|
|
26
23
|
|
|
@@ -28,10 +25,8 @@ You can install the full package or individual packages as needed:
|
|
|
28
25
|
pnpm add kt.js
|
|
29
26
|
|
|
30
27
|
# Or install individual packages
|
|
31
|
-
pnpm add @ktjs/core # Core DOM utilities (independent)
|
|
32
28
|
pnpm add @ktjs/router # Client-side router (requires @ktjs/core)
|
|
33
29
|
pnpm add @ktjs/mui # Material UI components (requires @ktjs/core)
|
|
34
|
-
pnpm add @ktjs/shortcuts # Shortcuts (requires @ktjs/core)
|
|
35
30
|
```
|
|
36
31
|
|
|
37
32
|
## Philosophy
|
|
@@ -40,78 +35,9 @@ As a web framework, repeatedly creating a large number of variables and objects
|
|
|
40
35
|
|
|
41
36
|
KT.js follows one rule: **full control of DOM and avoid unnecessary repainting**.
|
|
42
37
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
- **Monorepo Architecture**: Modular packages that can be installed independently or together
|
|
46
|
-
- **Tiny Bundle Size**: Minimal runtime overhead with aggressive tree-shaking
|
|
47
|
-
- **`h` function**: Create DOM elements with a simple, flexible API
|
|
48
|
-
- Shortcut functions for all HTML elements (`div`, `span`, `button`, etc.)
|
|
49
|
-
- Event handlers with `on:<eventName>` syntax or function attributes
|
|
50
|
-
- Full TypeScript support with intelligent type inference
|
|
51
|
-
- **JSX/TSX Support**: Full JSX syntax support with TypeScript integration
|
|
52
|
-
- Zero virtual DOM - JSX compiles directly to `h()` function calls
|
|
53
|
-
- Full HTML element type inference (`<button>` returns `HTMLButtonElement`)
|
|
54
|
-
- Support for `on:click` event handler syntax
|
|
55
|
-
- `redraw()` method for controlled component updates (v0.11+)
|
|
56
|
-
- `k-if` directive for conditional rendering (v0.14.6+)
|
|
57
|
-
- Array children support for seamless `.map()` integration (v0.14.1+)
|
|
58
|
-
- **List Rendering**: Efficient list rendering with `KTFor` component (v0.16.0+)
|
|
59
|
-
- Comment anchor with `__kt_for_list__` array property
|
|
60
|
-
- Key-based DOM reuse for minimal updates
|
|
61
|
-
- Auto-appends list items when anchor added to parent
|
|
62
|
-
- **Async Components**: Built-in support for Promise-based components
|
|
63
|
-
- `KTAsync` component for handling async operations
|
|
64
|
-
- Automatic placeholder management during loading
|
|
65
|
-
- Seamless integration with JSX/TSX syntax
|
|
66
|
-
- **Client-Side Router** (separate package):
|
|
67
|
-
- Hash-based routing only (simplified from v0.14.7+)
|
|
68
|
-
- Async navigation guards with Promise support
|
|
69
|
-
- Dynamic route parameters and query string parsing
|
|
70
|
-
- RouterView component for declarative routing
|
|
71
|
-
- Pure routing logic - no rendering, no dependencies
|
|
72
|
-
- **Shortcuts & Utilities**:
|
|
73
|
-
- `withDefaults`: Wrap element creation functions with default properties
|
|
74
|
-
- Convenient shorthand functions for common operations
|
|
75
|
-
- Form helpers and layout utilities
|
|
76
|
-
- **Full ES5 Compatibility**: Works in IE9+ and all modern browsers
|
|
77
|
-
- Transpiled to ES5 with no modern syntax
|
|
78
|
-
- Optional minimal Promise polyfill for older environments
|
|
79
|
-
- **Shared Runtime**: Efficient code sharing across packages with zero overhead
|
|
80
|
-
|
|
81
|
-
## Getting started
|
|
82
|
-
|
|
83
|
-
Install via package managers:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
npm install kt.js
|
|
87
|
-
# or
|
|
88
|
-
pnpm add kt.js
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
import { h, div } from 'kt.js';
|
|
93
|
-
|
|
94
|
-
const container = div('container', [div('header'), div('body', 'something'), div('footer')]);
|
|
95
|
-
const app = h('section', { id: 'app' }, container);
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
This will create the following DOM structure:
|
|
99
|
-
|
|
100
|
-
```html
|
|
101
|
-
<section id="app">
|
|
102
|
-
<div class="container">
|
|
103
|
-
<div class="header"></div>
|
|
104
|
-
<div class="body">something</div>
|
|
105
|
-
<div class="footer"></div>
|
|
106
|
-
</div>
|
|
107
|
-
</section>
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Using JSX/TSX
|
|
111
|
-
|
|
112
|
-
KT.js now has full JSX support! With the `@ktjs/jsx` package (included in the main `kt.js` package), you can write components using familiar JSX syntax:
|
|
38
|
+
## Configuration
|
|
113
39
|
|
|
114
|
-
|
|
40
|
+
for TSX/JSX support, add this to your `tsconfig.json`:
|
|
115
41
|
|
|
116
42
|
```json
|
|
117
43
|
{
|
|
@@ -122,332 +48,193 @@ KT.js now has full JSX support! With the `@ktjs/jsx` package (included in the ma
|
|
|
122
48
|
}
|
|
123
49
|
```
|
|
124
50
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
```tsx
|
|
128
|
-
import { jsx } from 'kt.js';
|
|
129
|
-
|
|
130
|
-
function Counter() {
|
|
131
|
-
const count = 0;
|
|
51
|
+
## Core Features (@ktjs/core)
|
|
132
52
|
|
|
133
|
-
|
|
134
|
-
<div class="counter">
|
|
135
|
-
<h1>Counter: {count}</h1>
|
|
136
|
-
<button on:click={() => console.log('Clicked!')}>Increment</button>
|
|
137
|
-
</div>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// JSX compiles to direct h() function calls - no virtual DOM!
|
|
142
|
-
const counterElement = <Counter />;
|
|
143
|
-
```
|
|
53
|
+
### JSX/TSX Support
|
|
144
54
|
|
|
145
|
-
|
|
55
|
+
KT.js provides first-class JSX/TSX support with zero virtual DOM overhead. JSX compiles directly to `h()` function calls.
|
|
146
56
|
|
|
147
57
|
```tsx
|
|
148
|
-
|
|
149
|
-
|
|
58
|
+
// Function components
|
|
59
|
+
const Button = ({ onClick, children }) => (
|
|
60
|
+
<button on:click={onClick} class="btn">
|
|
61
|
+
{children}
|
|
62
|
+
</button>
|
|
63
|
+
);
|
|
150
64
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
65
|
+
// Conditional rendering with k-if
|
|
66
|
+
const UserCard = ({ user, showDetails }) => (
|
|
67
|
+
<div class="card">
|
|
68
|
+
<h3>{user.name}</h3>
|
|
69
|
+
<div k-if={showDetails}>
|
|
70
|
+
<p>Email: {user.email}</p>
|
|
71
|
+
<p>Role: {user.role}</p>
|
|
154
72
|
</div>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
**Type Safety**:
|
|
160
|
-
|
|
161
|
-
```tsx
|
|
162
|
-
// TypeScript knows this is an HTMLButtonElement
|
|
163
|
-
const button: HTMLButtonElement = <button>Click</button>;
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
164
75
|
|
|
165
|
-
//
|
|
166
|
-
const
|
|
76
|
+
// List rendering with KTFor
|
|
77
|
+
const UserList = ({ users }) => <KTFor list={users} key={(user) => user.id} map={(user) => <UserCard user={user} />} />;
|
|
167
78
|
|
|
168
|
-
//
|
|
169
|
-
const
|
|
79
|
+
// Async components
|
|
80
|
+
const AsyncContent = async () => {
|
|
81
|
+
const data = await fetchData();
|
|
82
|
+
return <div>{data}</div>;
|
|
83
|
+
};
|
|
170
84
|
```
|
|
171
85
|
|
|
172
|
-
|
|
86
|
+
### Directives
|
|
173
87
|
|
|
174
|
-
|
|
175
|
-
- JSX compiles directly to `h()` function calls - **zero virtual DOM overhead**
|
|
176
|
-
- Use `on:click` syntax for event handlers to avoid conflicts with existing attributes
|
|
177
|
-
- All JSX elements have proper HTML element type inference in TypeScript
|
|
178
|
-
- Use `k-if` attribute for conditional rendering (v0.14.6+)
|
|
179
|
-
- Children can be arrays for easy `.map()` integration (v0.14.1+)
|
|
180
|
-
|
|
181
|
-
**Conditional Rendering with k-if** (v0.14.6+):
|
|
88
|
+
#### `k-if`: Conditional rendering.
|
|
182
89
|
|
|
183
90
|
```tsx
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
function UserProfile({ user, isLoggedIn }: { user: any; isLoggedIn: boolean }) {
|
|
187
|
-
return (
|
|
188
|
-
<div>
|
|
189
|
-
<h1>Profile</h1>
|
|
190
|
-
{/* Element only created if condition is true */}
|
|
191
|
-
<div k-if={isLoggedIn}>
|
|
192
|
-
<p>Welcome, {user.name}!</p>
|
|
193
|
-
<button>Logout</button>
|
|
194
|
-
</div>
|
|
195
|
-
{/* Element only created if condition is true */}
|
|
196
|
-
<div k-if={!isLoggedIn}>
|
|
197
|
-
<p>Please log in</p>
|
|
198
|
-
<button>Login</button>
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
);
|
|
202
|
-
}
|
|
91
|
+
<div k-if={condition}>This will render only if condition is true.</div>
|
|
203
92
|
```
|
|
204
93
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
```tsx
|
|
208
|
-
import { jsx } from 'kt.js';
|
|
209
|
-
|
|
210
|
-
function TodoList({ todos }: { todos: string[] }) {
|
|
211
|
-
return (
|
|
212
|
-
<div>
|
|
213
|
-
<h2>Todo List</h2>
|
|
214
|
-
<ul>
|
|
215
|
-
{/* Map arrays directly as children */}
|
|
216
|
-
{todos.map((todo) => (
|
|
217
|
-
<li>{todo}</li>
|
|
218
|
-
))}
|
|
219
|
-
</ul>
|
|
220
|
-
</div>
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Mix mapped elements with other elements
|
|
225
|
-
function MixedList({ items }: { items: string[] }) {
|
|
226
|
-
return (
|
|
227
|
-
<ul>
|
|
228
|
-
<li>Header Item</li>
|
|
229
|
-
{items.map((item) => (
|
|
230
|
-
<li>{item}</li>
|
|
231
|
-
))}
|
|
232
|
-
<li>Footer Item</li>
|
|
233
|
-
</ul>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
```
|
|
94
|
+
If a `ref` instance is bound, the element will automatically redraw on changes.
|
|
237
95
|
|
|
238
|
-
|
|
96
|
+
#### `k-model`: Two-way data binding for form elements.
|
|
239
97
|
|
|
240
|
-
|
|
98
|
+
Must bind to a `ref` instance.
|
|
241
99
|
|
|
242
100
|
```tsx
|
|
243
|
-
import {
|
|
244
|
-
|
|
245
|
-
// Define an async component that returns a Promise<HTMLElement>
|
|
246
|
-
const AsyncUserCard = function () {
|
|
247
|
-
return fetch('/api/user')
|
|
248
|
-
.then((res) => res.json())
|
|
249
|
-
.then((user) => (
|
|
250
|
-
<div class="user-card">
|
|
251
|
-
<h2>{user.name}</h2>
|
|
252
|
-
<p>{user.email}</p>
|
|
253
|
-
</div>
|
|
254
|
-
));
|
|
255
|
-
};
|
|
101
|
+
import { ref } from 'kt.js';
|
|
102
|
+
const name = ref('');
|
|
256
103
|
|
|
257
|
-
|
|
258
|
-
function App() {
|
|
104
|
+
function NameInput() {
|
|
259
105
|
return (
|
|
260
|
-
<div
|
|
261
|
-
<
|
|
262
|
-
<KTAsync component={AsyncUserCard} />
|
|
106
|
+
<div>
|
|
107
|
+
<input type="text" k-model={name} />
|
|
263
108
|
</div>
|
|
264
109
|
);
|
|
265
110
|
}
|
|
266
111
|
|
|
267
|
-
|
|
268
|
-
// When the Promise resolves, it automatically replaces with the actual element
|
|
112
|
+
name.value = 'New Name'; // Updates input value
|
|
269
113
|
```
|
|
270
114
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
1. Creates a placeholder comment node (`ktjs-suspense-placeholder`) immediately
|
|
274
|
-
2. Calls your component function (which should return a `Promise<HTMLElement>` or `HTMLElement`)
|
|
275
|
-
3. When the Promise resolves, automatically replaces the placeholder with the resolved element
|
|
276
|
-
4. If your component returns a non-Promise value, it's used directly without async handling
|
|
115
|
+
### CreateRedrawable
|
|
277
116
|
|
|
278
|
-
|
|
117
|
+
Create redrawable components with `createRedrawable()`.
|
|
279
118
|
|
|
280
119
|
```tsx
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
// Simulate async data loading
|
|
291
|
-
return new Promise<HTMLElement>((resolve) => {
|
|
292
|
-
setTimeout(() => resolve(container), 500);
|
|
293
|
-
});
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
// Usage
|
|
297
|
-
const app = (
|
|
298
|
-
<div>
|
|
299
|
-
<h1>Loading async content...</h1>
|
|
300
|
-
<KTAsync component={DynamicContent} />
|
|
301
|
-
</div>
|
|
302
|
-
);
|
|
120
|
+
import { createRedrawable } from '@ktjs/core';
|
|
121
|
+
let text = 'aa';
|
|
122
|
+
const el = createRedrawable(() => <div>{text}</div>);
|
|
123
|
+
// el is now `<div>aa</div>`
|
|
124
|
+
text = 'bb';
|
|
125
|
+
el.redraw();
|
|
126
|
+
// el is now `<div>bb</div>`
|
|
303
127
|
```
|
|
304
128
|
|
|
305
|
-
|
|
129
|
+
### Reactive References
|
|
306
130
|
|
|
307
|
-
|
|
308
|
-
const button = btn(
|
|
309
|
-
{
|
|
310
|
-
dblclick: '22',
|
|
311
|
-
'on:dblclick': function trueHandler() {
|
|
312
|
-
/* ... */
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
'Click me',
|
|
316
|
-
);
|
|
131
|
+
Create reactive values with `ref()` and listen to changes.
|
|
317
132
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
button.setAttribute('dblclick', '22');
|
|
321
|
-
button.addEventListener('click', () => alert('Clicked!'));
|
|
322
|
-
button.addEventListener('dblclick', function trueHandler() {
|
|
323
|
-
/* ... */
|
|
324
|
-
});
|
|
325
|
-
```
|
|
133
|
+
```typescript
|
|
134
|
+
import { ref } from '@ktjs/core';
|
|
326
135
|
|
|
327
|
-
|
|
136
|
+
const count = ref(0);
|
|
137
|
+
count.addOnChange((newVal, oldVal) => {
|
|
138
|
+
console.log(`Count changed from ${oldVal} to ${newVal}`);
|
|
139
|
+
});
|
|
328
140
|
|
|
329
|
-
|
|
141
|
+
// Update triggers change listeners
|
|
142
|
+
count.value = 1;
|
|
143
|
+
```
|
|
330
144
|
|
|
331
|
-
|
|
332
|
-
import { css } from '@emotion/css';
|
|
333
|
-
import { h, div } from 'kt.js';
|
|
145
|
+
#### Surface Reactive Objects
|
|
334
146
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
font-size: 20px;
|
|
338
|
-
`;
|
|
147
|
+
Create depth-1 reactive objects with `surfaceRef()`.
|
|
148
|
+
Basically a helper of wrapping each property with `ref()`.
|
|
339
149
|
|
|
340
|
-
|
|
341
|
-
|
|
150
|
+
```tsx
|
|
151
|
+
const obj = surfaceRef({ a: 1, b: 2 });
|
|
152
|
+
obj.a.value = 3; // Triggers change listeners for obj.a
|
|
342
153
|
|
|
343
|
-
//
|
|
344
|
-
div(className, 'Styled text');
|
|
154
|
+
const data = obj.kcollect(); // Deref to the original object { a: 3, b: 2 }
|
|
345
155
|
```
|
|
346
156
|
|
|
347
|
-
###
|
|
348
|
-
|
|
349
|
-
The `withDefaults` function allows you to create element factories with predefined properties:
|
|
157
|
+
### Redraw Mechanism
|
|
350
158
|
|
|
351
|
-
|
|
352
|
-
import { withDefaults, div, button } from 'kt.js';
|
|
159
|
+
`<KTFor... />` elements in-place with `redraw()` for minimal DOM updates.
|
|
353
160
|
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// Use them
|
|
359
|
-
const myCard = card('card-body', 'Content'); // <div class="card"><div class="card-body">Content</div></div>
|
|
360
|
-
const myBlueCard = blueCard('title', 'Blue!'); // <div class="card" style="background: blue"><div class="title">Blue!</div></div>
|
|
161
|
+
```tsx
|
|
162
|
+
const element = <KTFor list={someArray} map={(v) => 2 * v} />;
|
|
163
|
+
element.redraw({ list: [1, 2, 3] });
|
|
361
164
|
```
|
|
362
165
|
|
|
363
|
-
## Router
|
|
166
|
+
## Router (@ktjs/router)
|
|
167
|
+
|
|
168
|
+
Client-side hash-based routing with async navigation guards.
|
|
364
169
|
|
|
365
|
-
|
|
170
|
+
### Basic Routing
|
|
366
171
|
|
|
367
|
-
```
|
|
172
|
+
```typescript
|
|
368
173
|
import { createRouter } from '@ktjs/router';
|
|
369
|
-
import { div, h1 } from 'kt.js';
|
|
370
174
|
|
|
371
175
|
const router = createRouter({
|
|
372
176
|
routes: [
|
|
373
177
|
{
|
|
374
178
|
path: '/',
|
|
375
179
|
name: 'home',
|
|
376
|
-
|
|
377
|
-
// Render your page here
|
|
378
|
-
document.getElementById('app')!.innerHTML = '';
|
|
379
|
-
document.getElementById('app')!.appendChild(div({}, [h1({}, 'Home Page')]));
|
|
380
|
-
},
|
|
180
|
+
component: () => <HomePage />
|
|
381
181
|
},
|
|
382
182
|
{
|
|
383
183
|
path: '/user/:id',
|
|
384
184
|
name: 'user',
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
console.log('Entering user page');
|
|
388
|
-
document.getElementById('app')!.innerHTML = '';
|
|
389
|
-
document.getElementById('app')!.appendChild(div({}, [h1({}, `User ${to.params.id}`)]));
|
|
390
|
-
return true;
|
|
391
|
-
},
|
|
392
|
-
},
|
|
185
|
+
component: (to) => <UserPage id={to.params.id} />
|
|
186
|
+
}
|
|
393
187
|
],
|
|
394
188
|
beforeEach: async (to, from) => {
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
// Called after successful navigation
|
|
401
|
-
document.title = to.name || to.path;
|
|
402
|
-
},
|
|
403
|
-
onError: (error) => {
|
|
404
|
-
console.error('Router error:', error);
|
|
405
|
-
},
|
|
189
|
+
// Auth check
|
|
190
|
+
if (to.path === '/admin' && !isAdmin()) {
|
|
191
|
+
return '/login';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
406
194
|
});
|
|
195
|
+
```
|
|
407
196
|
|
|
408
|
-
|
|
409
|
-
router.push('/user/123');
|
|
410
|
-
router.push('/user/456?page=2');
|
|
197
|
+
### Navigation
|
|
411
198
|
|
|
412
|
-
|
|
413
|
-
|
|
199
|
+
```typescript
|
|
200
|
+
// Programmatic navigation
|
|
201
|
+
router.push('/user/123');
|
|
202
|
+
router.push({ name: 'user', params: { id: '456' } });
|
|
414
203
|
|
|
415
|
-
//
|
|
416
|
-
console.log(router.current?.path, router.current?.params
|
|
204
|
+
// Access current route
|
|
205
|
+
console.log(router.current?.path, router.current?.params);
|
|
417
206
|
```
|
|
418
207
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
- **Hash-based Routing Only** (v0.14.7+): Uses URL hash for client-side navigation (`#/path`)
|
|
422
|
-
- **Dynamic Parameters**: Support for dynamic route segments (`/user/:id`)
|
|
423
|
-
- **Query Strings**: Automatic parsing of query parameters (`?key=value`)
|
|
424
|
-
- **Named Routes**: Navigate using route names instead of paths
|
|
425
|
-
- **Async Navigation Guards**:
|
|
426
|
-
- `beforeEach`: Global guard before navigation (async)
|
|
427
|
-
- `beforeEnter`: Per-route guard (can also be used for rendering, async)
|
|
428
|
-
- `afterEach`: Global hook after navigation
|
|
429
|
-
- All guards support Promise-based async operations
|
|
430
|
-
- Guards can return `false` to cancel, string/object to redirect
|
|
431
|
-
- `GuardLevel` for fine-grained control over guard execution
|
|
432
|
-
- **Error Handling**: `onError` and `onNotFound` callbacks
|
|
433
|
-
- **Optimized Performance**: Pre-flattened routes and efficient matching algorithm
|
|
434
|
-
- **Zero Dependencies**: Fully self-contained router implementation (does not require `@ktjs/core` for runtime, only for TypeScript types)
|
|
435
|
-
- **Pure Routing**: No rendering logic - you control the DOM
|
|
436
|
-
- **Automatic Initialization**: Router auto-initializes on creation (v0.14.7+)
|
|
208
|
+
## Installation
|
|
437
209
|
|
|
438
|
-
|
|
210
|
+
```bash
|
|
211
|
+
# Full package (includes core)
|
|
212
|
+
pnpm add kt.js
|
|
439
213
|
|
|
440
|
-
|
|
214
|
+
# Individual packages
|
|
215
|
+
pnpm add @ktjs/core @ktjs/router
|
|
216
|
+
```
|
|
441
217
|
|
|
442
|
-
|
|
218
|
+
## TypeScript Configuration
|
|
443
219
|
|
|
444
|
-
For
|
|
220
|
+
For JSX/TSX support, configure your `tsconfig.json`:
|
|
445
221
|
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"compilerOptions": {
|
|
225
|
+
"jsx": "react-jsx",
|
|
226
|
+
"jsxImportSource": "@ktjs/core"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
449
229
|
```
|
|
450
230
|
|
|
231
|
+
## Philosophy
|
|
232
|
+
|
|
233
|
+
- **Direct DOM Manipulation**: No virtual DOM, minimal abstraction
|
|
234
|
+
- **Zero Re-renders**: Update only what changes, avoid full component re-renders
|
|
235
|
+
- **Type Safety**: Full TypeScript support with accurate type inference
|
|
236
|
+
- **Lightweight**: Small bundle size, no unnecessary dependencies
|
|
237
|
+
|
|
451
238
|
## License
|
|
452
239
|
|
|
453
240
|
MIT
|
package/dist/jsx-runtime.mjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
280
|
-
|
|
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
|
|
354
|
+
const maybeDummyRef = isKTRef(props.ref) ? props.ref : dummyRef;
|
|
321
355
|
let el;
|
|
322
|
-
if ('k-if' in props
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
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": "kt.js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
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.22.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "rollup -c rollup.config.mjs",
|