@ktjs/mui 0.20.2 → 0.23.2

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,14 +1,78 @@
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 $defines = Object.defineProperties;
7
+ const $entries = Object.entries;
8
+ const $random$2 = Math.random;
9
+ const { get: $buttonDisabledGetter$2, set: $buttonDisabledSetter$2 } = Object.getOwnPropertyDescriptor(HTMLButtonElement.prototype, 'disabled');
10
+ const parseStyle = (style) => {
11
+ if (!style) {
12
+ return '';
13
+ }
14
+ if (typeof style === 'string') {
15
+ return style;
16
+ }
17
+ if (style && typeof style === 'object') {
18
+ if (style.isKT) {
19
+ return parseStyle(style.value);
20
+ }
21
+ return $entries(style)
22
+ .map((entry) => {
23
+ const cssKey = entry[0].replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
24
+ return `${cssKey}:${entry[1]}`;
25
+ })
26
+ .join(';');
27
+ }
28
+ return '';
29
+ };
30
+
31
+ // String manipulation utilities
32
+ /**
33
+ * Default empty function
34
+ */
35
+ const $emptyFn = (() => true);
36
+
37
+ if (typeof Symbol === 'undefined') {
38
+ window.Symbol = function Symbol(description) {
39
+ return `@@SYMBOL_${description || ''}_${$random$2().toString(36).slice(2)}`;
40
+ };
41
+ }
42
+
43
+ // Shared utilities and cached native methods for kt.js framework
44
+ Object.defineProperty(window, '__ktjs__', { value: '0.23.4' });
45
+
46
+ // Shared constants
47
+ // Empty for now - can be extended with framework-wide constants
48
+ /**
49
+ * Mark the attribute as SVG to handle special cases during rendering.
50
+ */
51
+ const SVG_ATTR_FLAG = '__kt_svg__';
52
+ /**
53
+ * Mark the attribute as MathML to handle special cases during rendering.
54
+ */
55
+ const MATHML_ATTR_FLAG = '__kt_mathml__';
56
+
1
57
  // Cached native methods for performance optimization
2
58
  const $isArray = Array.isArray;
3
- const $isThenable = (o) => typeof o === 'object' && o !== null && typeof o.then === 'function';
4
-
5
- // Error handling utilities
6
- const $throw = (message) => {
7
- throw new Error('@ktjs/shared: ' + message);
8
- };
59
+ const $random$1 = Math.random;
60
+ const $isThenable = (o) => typeof o?.then === 'function';
9
61
 
10
62
  // DOM manipulation utilities
11
63
  // # dom natives
64
+ const $isNode$1 = (x) => x?.nodeType > 0;
65
+ /**
66
+ * Safe replace `oldNode` With `newNode`
67
+ */
68
+ const $replaceNode$1 = (oldNode, newNode) => {
69
+ if ($isNode$1(oldNode) && $isNode$1(newNode)) {
70
+ if (newNode.contains(oldNode)) {
71
+ newNode.remove();
72
+ }
73
+ oldNode.replaceWith(newNode);
74
+ }
75
+ };
12
76
  /**
13
77
  * & Remove `bind` because it is shockingly slower than wrapper
14
78
  * & `window.document` is safe because it is not configurable and its setter is undefined
@@ -44,11 +108,28 @@ const $append = // for ie 9/10/11
44
108
  $appendChild.call(this, fragment);
45
109
  }
46
110
  };
47
- const { get: $buttonDisabledGetter$2, set: $buttonDisabledSetter$2 } = Object.getOwnPropertyDescriptor(HTMLButtonElement.prototype, 'disabled');
111
+ const { get: $buttonDisabledGetter$1, set: $buttonDisabledSetter$1 } = Object.getOwnPropertyDescriptor(HTMLButtonElement.prototype, 'disabled');
112
+ /**
113
+ * Used for `k-model`
114
+ */
115
+ const applyModel = (element, valueRef, propName, eventName) => {
116
+ element[propName] = valueRef.value; // initialize
117
+ valueRef.addOnChange((newValue) => (element[propName] = newValue));
118
+ element.addEventListener(eventName, () => (valueRef.value = element[propName]));
119
+ };
120
+
121
+ if (typeof Symbol === 'undefined') {
122
+ window.Symbol = function Symbol(description) {
123
+ return `@@SYMBOL_${description || ''}_${$random$1().toString(36).slice(2)}`;
124
+ };
125
+ }
48
126
 
49
127
  // Shared utilities and cached native methods for kt.js framework
50
- // Re-export all utilities
51
- Object.defineProperty(window, '@ktjs/shared', { value: '0.20.0' });
128
+ Object.defineProperty(window, '__ktjs__', { value: '0.23.4' });
129
+
130
+ const isKT$1 = (obj) => obj?.isKT;
131
+ const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
132
+ const isComputed = (obj) => obj?.ktType === 2 /* KTReactiveType.COMPUTED */;
52
133
 
53
134
  const booleanHandler = (element, key, value) => {
54
135
  if (key in element) {
@@ -92,10 +173,25 @@ const handlers = {
92
173
  };
93
174
 
94
175
  const defaultHandler = (element, key, value) => element.setAttribute(key, value);
176
+ const setElementStyle = (element, style) => {
177
+ if (typeof style === 'string') {
178
+ element.style.cssText = style;
179
+ return;
180
+ }
181
+ for (const key in style) {
182
+ element.style[key] = style[key];
183
+ }
184
+ };
95
185
  function attrIsObject(element, attr) {
96
186
  const classValue = attr.class || attr.className;
97
187
  if (classValue !== undefined) {
98
- element.setAttribute('class', classValue);
188
+ if (isKT$1(classValue)) {
189
+ element.setAttribute('class', classValue.value);
190
+ classValue.addOnChange((v) => element.setAttribute('class', v));
191
+ }
192
+ else {
193
+ element.setAttribute('class', classValue);
194
+ }
99
195
  }
100
196
  const style = attr.style;
101
197
  if (style) {
@@ -103,18 +199,34 @@ function attrIsObject(element, attr) {
103
199
  element.setAttribute('style', style);
104
200
  }
105
201
  else if (typeof style === 'object') {
106
- for (const key in style) {
107
- element.style[key] = style[key];
202
+ if (isKT$1(style)) {
203
+ setElementStyle(element, style.value);
204
+ style.addOnChange((v) => setElementStyle(element, v));
205
+ }
206
+ else {
207
+ setElementStyle(element, style);
108
208
  }
109
209
  }
110
210
  }
211
+ if ('k-html' in attr) {
212
+ const html = attr['k-html'];
213
+ if (isKT$1(html)) {
214
+ element.innerHTML = html.value;
215
+ html.addOnChange((v) => (element.innerHTML = v));
216
+ }
217
+ else {
218
+ element.innerHTML = html;
219
+ }
220
+ }
111
221
  for (const key in attr) {
112
- if (key === 'class' ||
222
+ if (key === 'k-if' ||
223
+ key === 'k-model' ||
224
+ key === 'ref' ||
225
+ key === 'class' ||
113
226
  key === 'className' ||
114
227
  key === 'style' ||
115
228
  key === 'children' ||
116
- key === 'k-if' ||
117
- key === 'ref') {
229
+ key === 'k-html') {
118
230
  continue;
119
231
  }
120
232
  const o = attr[key];
@@ -124,7 +236,14 @@ function attrIsObject(element, attr) {
124
236
  }
125
237
  // normal attributes
126
238
  else {
127
- (handlers[key] || defaultHandler)(element, key, o);
239
+ const handler = handlers[key] || defaultHandler;
240
+ if (isKT$1(o)) {
241
+ handler(element, key, o.value);
242
+ o.addOnChange((v) => handler(element, key, v));
243
+ }
244
+ else {
245
+ handler(element, key, o);
246
+ }
128
247
  }
129
248
  }
130
249
  }
@@ -136,17 +255,28 @@ function applyAttr(element, attr) {
136
255
  attrIsObject(element, attr);
137
256
  }
138
257
  else {
139
- throw new Error('kt.js: attr must be an object.');
258
+ throw new Error('[kt.js error] attr must be an object.');
140
259
  }
141
260
  }
142
261
 
262
+ const assureNode = (o) => ($isNode$1(o) ? o : document.createTextNode(o));
143
263
  function apdSingle(element, c) {
144
264
  // & JSX should ignore false, undefined, and null
145
265
  if (c === false || c === undefined || c === null) {
146
266
  return;
147
267
  }
148
- if (typeof c === 'object' && c !== null && 'isKT' in c) {
149
- $append.call(element, c.value);
268
+ if (isKT$1(c)) {
269
+ let node = assureNode(c.value);
270
+ $append.call(element, node);
271
+ c.addOnChange((newValue, oldValue) => {
272
+ if ($isNode$1(newValue) && $isNode$1(oldValue)) {
273
+ // & this case is handled automically in `class KTRef`
274
+ return;
275
+ }
276
+ const oldNode = node;
277
+ node = assureNode(newValue);
278
+ oldNode.replaceWith(node);
279
+ });
150
280
  }
151
281
  else {
152
282
  $append.call(element, c);
@@ -191,14 +321,34 @@ function applyContent(element, content) {
191
321
  }
192
322
  }
193
323
 
194
- document.createElement('div');
324
+ function applyKModel(element, valueRef) {
325
+ if (!isKT$1(valueRef)) {
326
+ console.warn('[kt.js warn]','k-model value must be a KTRef.');
327
+ return;
328
+ }
329
+ if (element instanceof HTMLInputElement) {
330
+ if (element.type === 'radio' || element.type === 'checkbox') {
331
+ applyModel(element, valueRef, 'checked', 'change');
332
+ }
333
+ else {
334
+ applyModel(element, valueRef, 'value', 'input');
335
+ }
336
+ }
337
+ else if (element instanceof HTMLSelectElement) {
338
+ applyModel(element, valueRef, 'value', 'change');
339
+ }
340
+ else if (element instanceof HTMLTextAreaElement) {
341
+ applyModel(element, valueRef, 'value', 'input');
342
+ }
343
+ else {
344
+ console.warn('[kt.js warn]','not supported element for k-model:');
345
+ }
346
+ }
347
+
195
348
  const htmlCreator = (tag) => document.createElement(tag);
196
349
  const svgCreator = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
197
350
  const mathMLCreator = (tag) => document.createElementNS('http://www.w3.org/1998/Math/MathML', tag);
198
351
  let creator = htmlCreator;
199
- // # consts
200
- const SVG_ATTR_FLAG = '__kt_svg__';
201
- const MATHML_ATTR_FLAG = '__kt_mathml__';
202
352
  /**
203
353
  * Create an enhanced HTMLElement.
204
354
  * - Only supports HTMLElements, **NOT** SVGElements or other Elements.
@@ -209,7 +359,7 @@ const MATHML_ATTR_FLAG = '__kt_mathml__';
209
359
  * ## About
210
360
  * @package @ktjs/core
211
361
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
212
- * @version 0.20.3 (Last Update: 2026.02.01 18:38:02.198)
362
+ * @version 0.26.9 (Last Update: 2026.02.09 00:03:34.324)
213
363
  * @license MIT
214
364
  * @link https://github.com/baendlorel/kt.js
215
365
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -218,7 +368,7 @@ const MATHML_ATTR_FLAG = '__kt_mathml__';
218
368
  */
219
369
  const h = (tag, attr, content) => {
220
370
  if (typeof tag !== 'string') {
221
- $throw('tagName must be a string.');
371
+ throw new Error('[kt.js error] tagName must be a string.');
222
372
  }
223
373
  if (attr) {
224
374
  if (SVG_ATTR_FLAG in attr) {
@@ -238,39 +388,57 @@ const h = (tag, attr, content) => {
238
388
  // * Handle content
239
389
  applyAttr(element, attr);
240
390
  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
- // }
391
+ if (typeof attr === 'object' && attr !== null && 'k-model' in attr) {
392
+ applyKModel(element, attr['k-model']);
393
+ }
249
394
  return element;
250
395
  };
251
396
 
252
397
  const dummyRef = { value: null };
398
+ const create = (tag, props) => {
399
+ if (typeof tag === 'function') {
400
+ return tag(props);
401
+ }
402
+ else {
403
+ return h(tag, props, props.children);
404
+ }
405
+ };
406
+ const placeholder = () => document.createComment('k-if');
253
407
  /**
254
408
  * @param tag html tag or function component
255
409
  * @param props properties/attributes
256
410
  */
257
411
  function jsx(tag, props) {
258
- const ref = props.ref?.isKT ? props.ref : dummyRef;
259
- let el;
260
- if ('k-if' in props && !props['k-if']) {
261
- // & make comment placeholder in case that ref might be redrawn later
262
- el = document.createComment('k-if');
263
- ref.value = el;
264
- return el;
265
- }
266
- // Handle function components
267
- if (typeof tag === 'function') {
268
- el = tag(props);
412
+ if (isComputed(props.ref)) {
413
+ throw new Error('[kt.js error] Cannot assign a computed value to an element.');
269
414
  }
270
- else {
271
- el = h(tag, props, props.children);
415
+ const maybeDummyRef = isRef(props.ref) ? props.ref : dummyRef;
416
+ let el;
417
+ if ('k-if' in props) {
418
+ const kif = props['k-if'];
419
+ let condition = kif; // assume boolean by default
420
+ // Handle reactive k-if
421
+ if (isKT$1(kif)) {
422
+ kif.addOnChange((newValue, oldValue) => {
423
+ if (newValue === oldValue) {
424
+ return;
425
+ }
426
+ const oldEl = el;
427
+ el = newValue ? create(tag, props) : placeholder();
428
+ $replaceNode$1(oldEl, el);
429
+ maybeDummyRef.value = el;
430
+ });
431
+ condition = kif.value;
432
+ }
433
+ if (!condition) {
434
+ // & make comment placeholder in case that ref might be redrawn later
435
+ el = placeholder();
436
+ maybeDummyRef.value = el;
437
+ return el;
438
+ }
272
439
  }
273
- ref.value = el;
440
+ el = create(tag, props);
441
+ maybeDummyRef.value = el;
274
442
  return el;
275
443
  }
276
444
  /**
@@ -279,48 +447,8 @@ function jsx(tag, props) {
279
447
  */
280
448
  const jsxs = jsx;
281
449
 
282
- // Cached native methods for performance optimization
283
- const $defines = Object.defineProperties;
284
-
285
- // String manipulation utilities
286
- /**
287
- * Default empty function
288
- */
289
- const $emptyFn = (() => true);
290
- const { get: $buttonDisabledGetter$1, set: $buttonDisabledSetter$1 } = Object.getOwnPropertyDescriptor(HTMLButtonElement.prototype, 'disabled');
291
- // # DOM utilities
292
- const parseStyle = (style) => {
293
- if (typeof style === 'string') {
294
- return style;
295
- }
296
- if (style && typeof style === 'object') {
297
- return Object.entries(style)
298
- .map(([key, value]) => {
299
- // Convert camelCase to kebab-case
300
- const cssKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
301
- return `${cssKey}: ${value}`;
302
- })
303
- .join('; ');
304
- }
305
- return '';
306
- };
307
- const generateHandler = (props, key) => {
308
- const handler = props[key];
309
- if (typeof handler === 'function') {
310
- return handler;
311
- }
312
- else if (handler && typeof handler === 'object' && handler.isKT) {
313
- return (value) => (handler.value = value);
314
- }
315
- return $emptyFn;
316
- };
317
-
318
- // Shared utilities and cached native methods for kt.js framework
319
- // Re-export all utilities
320
- Object.defineProperty(window, '@ktjs/shared', { value: '0.20.0' });
321
-
322
450
  function Alert(props) {
323
- const { children, severity = 'info', variant = 'standard', icon, 'kt:close': onClose } = props;
451
+ const { children, severity = 'info', variant = 'standard', icon, 'on:close': onClose } = props;
324
452
  const classes = `mui-alert mui-alert-${severity} mui-alert-${variant} ${props.class ? props.class : ''}`;
325
453
  // Icon SVG paths for different severities
326
454
  const getIcon = () => {
@@ -349,201 +477,409 @@ function Alert(props) {
349
477
  return alert;
350
478
  }
351
479
 
480
+ // Shared constants
481
+ // Empty for now - can be extended with framework-wide constants
352
482
  /**
353
- * Button component - mimics MUI Button appearance and behavior
483
+ * Mark the attribute as SVG to handle special cases during rendering.
354
484
  */
355
- function Button(props) {
356
- let { children, variant = 'text', color = 'primary', size = 'medium', disabled = false, fullWidth = false, iconOnly = false, startIcon, endIcon, type = 'button', 'on:click': onClick = $emptyFn, } = props;
357
- const classes = [
358
- 'mui-button',
359
- `mui-button-${variant}`,
360
- `mui-button-${variant}-${color}`,
361
- `mui-button-size-${size}`,
362
- fullWidth ? 'mui-button-fullwidth' : '',
363
- iconOnly ? 'mui-button-icon-only' : '',
364
- disabled ? 'mui-button-disabled' : '',
365
- props.class ? props.class : '',
366
- ].join(' ');
367
- const rippleContainer = jsx("span", { class: "mui-button-ripple" });
368
- const handleClick = (e) => {
369
- if (disabled) {
370
- e.preventDefault();
371
- return;
372
- }
373
- // Create ripple effect
374
- const button = e.currentTarget;
375
- if (rippleContainer) {
376
- const rect = button.getBoundingClientRect();
377
- const size = Math.max(rect.width, rect.height);
378
- const x = e.clientX - rect.left - size / 2;
379
- const y = e.clientY - rect.top - size / 2;
380
- const ripple = document.createElement('span');
381
- ripple.style.width = ripple.style.height = `${size}px`;
382
- ripple.style.left = `${x}px`;
383
- ripple.style.top = `${y}px`;
384
- ripple.classList.add('mui-button-ripple-effect');
385
- rippleContainer.appendChild(ripple);
386
- // Remove ripple after animation
387
- setTimeout(() => ripple.remove(), 600);
388
- }
389
- onClick(e);
390
- };
391
- const container = (jsxs("button", { class: classes, style: parseStyle(props.style), type: type, disabled: disabled, "on:click": handleClick, children: [startIcon && jsx("span", { class: "mui-button-start-icon", children: startIcon }), jsx("span", { class: "mui-button-label", children: children }), endIcon && jsx("span", { class: "mui-button-end-icon", children: endIcon }), rippleContainer] }));
392
- $defines(container, {
393
- disabled: {
394
- get: $buttonDisabledGetter$1,
395
- set: function (value) {
396
- $buttonDisabledSetter$1.call(this, value);
397
- this.classList.toggle('mui-button-disabled', value);
398
- },
399
- },
400
- });
401
- return container;
402
- }
485
+ const $is = Object.is;
486
+ const $random = Math.random;
403
487
 
488
+ // DOM manipulation utilities
489
+ // # dom natives
490
+ const $isNode = (x) => x?.nodeType > 0;
404
491
  /**
405
- * Checkbox component - mimics MUI Checkbox appearance and behavior
492
+ * Safe replace `oldNode` With `newNode`
406
493
  */
407
- function Checkbox(props) {
408
- const toggleIcon = (checked, indeterminate) => {
409
- if (indeterminate) {
410
- uncheckedIcon.style.display = 'none';
411
- checkedIcon.style.display = 'none';
412
- indeterminateIcon.style.display = '';
413
- }
414
- else {
415
- uncheckedIcon.style.display = checked ? 'none' : '';
416
- checkedIcon.style.display = checked ? '' : 'none';
417
- indeterminateIcon.style.display = 'none';
494
+ const $replaceNode = (oldNode, newNode) => {
495
+ if ($isNode(oldNode) && $isNode(newNode)) {
496
+ if (newNode.contains(oldNode)) {
497
+ newNode.remove();
418
498
  }
499
+ oldNode.replaceWith(newNode);
500
+ }
501
+ };
502
+ const { get: $buttonDisabledGetter, set: $buttonDisabledSetter } = Object.getOwnPropertyDescriptor(HTMLButtonElement.prototype, 'disabled');
503
+
504
+ if (typeof Symbol === 'undefined') {
505
+ window.Symbol = function Symbol(description) {
506
+ return `@@SYMBOL_${description || ''}_${$random().toString(36).slice(2)}`;
419
507
  };
420
- // Handle change
421
- const handleChange = () => {
422
- if (disabled) {
508
+ }
509
+
510
+ // Shared utilities and cached native methods for kt.js framework
511
+ Object.defineProperty(window, '__ktjs__', { value: '0.23.4' });
512
+
513
+ const isKT = (obj) => obj?.isKT;
514
+
515
+ class KTRef {
516
+ /**
517
+ * Indicates that this is a KTRef instance
518
+ */
519
+ isKT = true;
520
+ ktType = 1 /* KTReactiveType.REF */;
521
+ /**
522
+ * @internal
523
+ */
524
+ _value;
525
+ /**
526
+ * @internal
527
+ */
528
+ _onChanges;
529
+ constructor(_value, _onChanges) {
530
+ this._value = _value;
531
+ this._onChanges = _onChanges;
532
+ }
533
+ /**
534
+ * If new value and old value are both nodes, the old one will be replaced in the DOM
535
+ */
536
+ get value() {
537
+ return this._value;
538
+ }
539
+ set value(newValue) {
540
+ if ($is(newValue, this._value)) {
423
541
  return;
424
542
  }
425
- checked = inputEl.checked;
426
- indeterminate = false;
427
- toggleIcon(checked, indeterminate);
428
- onChange(checked, value);
429
- };
430
- let { checked = false, value = '', label = '', size = 'medium', disabled = false, color = 'primary', indeterminate = false, } = props;
431
- const onChange = generateHandler(props, 'kt:change');
432
- const inputEl = (jsx("input", { type: "checkbox", class: "mui-checkbox-input", checked: checked, value: value, disabled: disabled, "on:change": handleChange }));
433
- // Unchecked icon
434
- const uncheckedIcon = (jsx("span", { class: "mui-checkbox-icon-unchecked", children: jsx("svg", { viewBox: "0 0 24 24", children: jsx("path", { d: "M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" }) }) }));
435
- // Checked icon
436
- const checkedIcon = (jsx("span", { class: "mui-checkbox-icon-checked", children: jsx("svg", { viewBox: "0 0 24 24", children: jsx("path", { d: "M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) }) }));
437
- // Indeterminate icon
438
- const indeterminateIcon = (jsx("span", { class: "mui-checkbox-icon-indeterminate", children: jsx("svg", { viewBox: "0 0 24 24", children: jsx("path", { d: "M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10H7v-2h10v2z" }) }) }));
439
- // Initialize icon state
440
- toggleIcon(checked, indeterminate);
441
- const container = (jsxs("label", { class: `mui-checkbox-wrapper mui-checkbox-size-${size} ${disabled ? 'mui-checkbox-disabled' : ''} mui-checkbox-color-${color}`, children: [inputEl, jsxs("span", { class: "mui-checkbox-icon", children: [uncheckedIcon, checkedIcon, indeterminateIcon] }), jsx("span", { "k-if": label, class: "mui-checkbox-label", children: label })] }));
442
- $defines(container, {
443
- checked: {
444
- get() {
445
- return checked;
446
- },
447
- set(newChecked) {
448
- checked = newChecked;
449
- indeterminate = false;
450
- inputEl.checked = checked;
451
- toggleIcon(checked, indeterminate);
452
- },
453
- },
454
- value: {
455
- get() {
456
- return value;
457
- },
458
- set(newValue) {
459
- value = newValue;
460
- inputEl.value = value;
461
- },
462
- },
463
- disabled: {
464
- get() {
465
- return disabled;
466
- },
467
- set(newDisabled) {
468
- disabled = Boolean(newDisabled);
469
- inputEl.disabled = disabled;
470
- container.classList.toggle('mui-checkbox-disabled', disabled);
471
- },
472
- },
473
- });
474
- return container;
543
+ const oldValue = this._value;
544
+ $replaceNode(oldValue, newValue);
545
+ this._value = newValue;
546
+ for (let i = 0; i < this._onChanges.length; i++) {
547
+ this._onChanges[i](newValue, oldValue);
548
+ }
549
+ }
550
+ /**
551
+ * Register a callback when the value changes
552
+ * @param callback (newValue, oldValue) => xxx
553
+ */
554
+ addOnChange(callback) {
555
+ if (typeof callback !== 'function') {
556
+ throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
557
+ }
558
+ this._onChanges.push(callback);
559
+ }
560
+ removeOnChange(callback) {
561
+ for (let i = this._onChanges.length - 1; i >= 0; i--) {
562
+ if (this._onChanges[i] === callback) {
563
+ this._onChanges.splice(i, 1);
564
+ return true;
565
+ }
566
+ }
567
+ return false;
568
+ }
475
569
  }
476
570
  /**
477
- * CheckboxGroup component - groups multiple checkboxes together
571
+ * Reference to the created HTML element.
572
+ * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
573
+ * - can alse be used to store normal values, but it is not reactive.
574
+ * - if the value is already a `KTRef`, it will be returned **directly**.
575
+ * @param value mostly an HTMLElement
478
576
  */
479
- function CheckboxGroup(props) {
480
- let { value = [], size = 'medium', row = false } = props;
481
- const onChange = generateHandler(props, 'kt:change');
482
- let selectedValues = new Set(value);
483
- const changeHandler = (checked, checkboxValue) => {
484
- if (checked) {
485
- selectedValues.add(checkboxValue);
486
- }
487
- else {
488
- selectedValues.delete(checkboxValue);
577
+ function ref(value, onChange) {
578
+ return new KTRef(value, onChange ? [onChange] : []);
579
+ }
580
+ function deref(value) {
581
+ return isKT(value) ? value.value : value;
582
+ }
583
+ // # asserts
584
+ /**
585
+ * Assert k-model to be a ref object
586
+ */
587
+ const $modelOrRef = (props, defaultValue) => {
588
+ // & props is an object. Won't use it in any other place
589
+ if ('k-model' in props) {
590
+ const kmodel = props['k-model'];
591
+ if (!kmodel?.isKT) {
592
+ throw new Error(`[kt.js error] k-model data must be a KTRef object, please use 'ref(...)' to wrap it.`);
489
593
  }
490
- onChange(Array.from(selectedValues));
491
- };
492
- const checkboxes = props.options.map((o) => {
493
- o.size = size;
494
- o.checked = selectedValues.has(o.value);
495
- const originalChange = generateHandler(o, 'kt:change');
496
- if (originalChange) {
497
- o['kt:change'] = (checked, value) => {
498
- originalChange(checked, value);
499
- changeHandler(checked, value);
500
- };
594
+ return kmodel;
595
+ }
596
+ return ref(defaultValue);
597
+ };
598
+
599
+ class KTComputed {
600
+ /**
601
+ * Indicates that this is a KTRef instance
602
+ */
603
+ isKT = true;
604
+ ktType = 2 /* KTReactiveType.COMPUTED */;
605
+ /**
606
+ * @internal
607
+ */
608
+ _calculator;
609
+ /**
610
+ * @internal
611
+ */
612
+ _value;
613
+ /**
614
+ * @internal
615
+ */
616
+ _onChanges = [];
617
+ /**
618
+ * @internal
619
+ */
620
+ _subscribe(reactives) {
621
+ for (let i = 0; i < reactives.length; i++) {
622
+ const reactive = reactives[i];
623
+ reactive.addOnChange(() => {
624
+ const oldValue = this._value;
625
+ this._value = this._calculator();
626
+ if (oldValue === this._value) {
627
+ return;
628
+ }
629
+ $replaceNode(oldValue, this._value);
630
+ for (let i = 0; i < this._onChanges.length; i++) {
631
+ this._onChanges[i](this._value, oldValue);
632
+ }
633
+ });
634
+ }
635
+ }
636
+ constructor(_calculator, reactives) {
637
+ this._calculator = _calculator;
638
+ this._value = _calculator();
639
+ this._subscribe(reactives);
640
+ }
641
+ /**
642
+ * If new value and old value are both nodes, the old one will be replaced in the DOM
643
+ */
644
+ get value() {
645
+ return this._value;
646
+ }
647
+ set value(_newValue) {
648
+ throw new Error('[kt.js error] KTComputed: cannot set value of a computed value');
649
+ }
650
+ /**
651
+ * Register a callback when the value changes
652
+ * @param callback (newValue, oldValue) => xxx
653
+ */
654
+ addOnChange(callback) {
655
+ if (typeof callback !== 'function') {
656
+ throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
657
+ }
658
+ this._onChanges.push(callback);
659
+ }
660
+ /**
661
+ * Unregister a callback
662
+ * @param callback (newValue, oldValue) => xxx
663
+ */
664
+ removeOnChange(callback) {
665
+ for (let i = this._onChanges.length - 1; i >= 0; i--) {
666
+ if (this._onChanges[i] === callback) {
667
+ this._onChanges.splice(i, 1);
668
+ return true;
669
+ }
670
+ }
671
+ return false;
672
+ }
673
+ }
674
+ /**
675
+ * Create a reactive computed value
676
+ * @param computeFn
677
+ * @param reactives refs and computeds that this computed depends on
678
+ */
679
+ function computed(computeFn, reactives) {
680
+ if (reactives.some((v) => !isKT(v))) {
681
+ throw new Error('[kt.js error] computed: all reactives must be KTRef or KTComputed instances');
682
+ }
683
+ return new KTComputed(computeFn, reactives);
684
+ }
685
+
686
+ const toReactive = (value, onChange) => {
687
+ if (isKT(value)) {
688
+ if (onChange) {
689
+ value.addOnChange(onChange);
690
+ }
691
+ return value;
692
+ }
693
+ else {
694
+ return ref(value, onChange);
695
+ }
696
+ };
697
+
698
+ const registerPrefixedEventsForButton = (element, props) => {
699
+ for (const key in props) {
700
+ if (key.startsWith('on:') && key !== 'on:click' && key !== 'on:dblclick') {
701
+ element.addEventListener(key.slice(3), props[key]);
702
+ }
703
+ }
704
+ };
705
+
706
+ /**
707
+ * Button component - mimics MUI Button appearance and behavior
708
+ */
709
+ function Button(props) {
710
+ let { children, variant = 'text', color = 'primary', size = 'medium', disabled = false, fullWidth = false, iconOnly = false, startIcon, endIcon, type = 'button', 'on:click': onClick = $emptyFn, // & must be bound because of the ripple effect
711
+ } = props;
712
+ const updateClass = () => {
713
+ container.className = [
714
+ 'mui-button',
715
+ `mui-button-${variant}`,
716
+ `mui-button-${variant}-${color}`,
717
+ `mui-button-size-${size}`,
718
+ fullWidth ? 'mui-button-fullwidth' : '',
719
+ iconOnly ? 'mui-button-icon-only' : '',
720
+ disabledRef.value ? 'mui-button-disabled' : '',
721
+ props.class ? props.class : '',
722
+ ].join(' ');
723
+ };
724
+ const disabledRef = toReactive(disabled, (v) => {
725
+ container.disabled = v;
726
+ updateClass();
727
+ });
728
+ const rippleContainer = jsx("span", { class: "mui-button-ripple" });
729
+ const createRippleEffect = (mouseX, mouseY) => {
730
+ const rect = container.getBoundingClientRect();
731
+ const size = Math.max(rect.width, rect.height);
732
+ const x = mouseX - rect.left - size / 2;
733
+ const y = mouseY - rect.top - size / 2;
734
+ const ripple = (jsx("span", { class: "mui-button-ripple-effect", style: `width:${size}px; height:${size}px; left:${x}px; top:${y}px;` }));
735
+ rippleContainer.appendChild(ripple);
736
+ setTimeout(() => ripple.remove(), 600); // Remove ripple after animation
737
+ };
738
+ const handleClick = (e) => {
739
+ if (disabledRef.value) {
740
+ e.preventDefault();
741
+ return;
742
+ }
743
+ createRippleEffect(e.clientX, e.clientY);
744
+ onClick(e);
745
+ };
746
+ const container = (jsxs("button", { style: parseStyle(props.style), type: type, disabled: disabledRef.value, "on:click": handleClick, children: [jsx("span", { "k-if": startIcon, class: "mui-button-start-icon", children: startIcon }), jsx("span", { class: "mui-button-label", children: children }), jsx("span", { "k-if": endIcon, class: "mui-button-end-icon", children: endIcon }), rippleContainer] }));
747
+ const onDblclick = props['on:dblclick'];
748
+ if (onDblclick) {
749
+ container.addEventListener('dblclick', (e) => {
750
+ if (disabledRef.value) {
751
+ e.preventDefault();
752
+ return;
753
+ }
754
+ createRippleEffect(e.clientX, e.clientY);
755
+ onDblclick(e);
756
+ });
757
+ }
758
+ registerPrefixedEventsForButton(container, props);
759
+ // # initialize
760
+ updateClass();
761
+ return container;
762
+ }
763
+
764
+ const $ArrayPushUnique = (arr, item) => {
765
+ if (!arr.includes(item)) {
766
+ arr.push(item);
767
+ }
768
+ };
769
+ const $ArrayDelete = (arr, item) => {
770
+ const index = arr.indexOf(item);
771
+ if (index > -1) {
772
+ arr.splice(index, 1);
773
+ }
774
+ };
775
+
776
+ const createUncheckedIcon = () => (jsx("span", { class: "mui-checkbox-icon-unchecked", children: jsx("svg", { viewBox: "0 0 24 24", children: jsx("path", { d: "M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" }) }) }));
777
+ const createCheckedIcon = () => (jsx("span", { class: "mui-checkbox-icon-checked", children: jsx("svg", { viewBox: "0 0 24 24", children: jsx("path", { d: "M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) }) }));
778
+ const createIndeterminateIcon = () => (jsx("span", { class: "mui-checkbox-icon-indeterminate", children: jsx("svg", { viewBox: "0 0 24 24", children: jsx("path", { d: "M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10H7v-2h10v2z" }) }) }));
779
+ /**
780
+ * Checkbox component - mimics MUI Checkbox appearance and behavior
781
+ */
782
+ function Checkbox(props) {
783
+ const toggleIcon = (checked, indeterminate) => {
784
+ if (indeterminate) {
785
+ uncheckedIcon.style.display = 'none';
786
+ checkedIcon.style.display = 'none';
787
+ indeterminateIcon.style.display = '';
501
788
  }
502
789
  else {
503
- o['kt:change'] = changeHandler;
790
+ uncheckedIcon.style.display = checked ? 'none' : '';
791
+ checkedIcon.style.display = checked ? '' : 'none';
792
+ indeterminateIcon.style.display = 'none';
793
+ }
794
+ };
795
+ // Handle change
796
+ const handleChange = () => {
797
+ if (disabledRef.value) {
798
+ return;
504
799
  }
505
- return Checkbox(o);
800
+ modelRef.value = inputEl.checked;
801
+ interminateRef.value = false;
802
+ toggleIcon(modelRef.value, interminateRef.value);
803
+ onChange(modelRef.value, valueRef.value);
804
+ };
805
+ const onChange = props['on:change'] ?? $emptyFn;
806
+ const labelRef = toReactive(props.label ?? '');
807
+ const valueRef = toReactive(props.value ?? '');
808
+ const interminateRef = toReactive(props.indeterminate ?? false);
809
+ const colorRef = toReactive(props.color ?? 'primary');
810
+ const sizeRef = toReactive(props.size ?? 'medium');
811
+ const disabledRef = toReactive(props.disabled ?? false, (v) => {
812
+ inputEl.disabled = v;
813
+ container.classList.toggle('mui-checkbox-disabled', v);
506
814
  });
507
- const container = (jsx("div", { class: `mui-checkbox-group ${row ? 'mui-checkbox-group-row' : ''} ${props.class ? props.class : ''}`, style: props.style ? props.style : '', role: "group", children: checkboxes }));
508
- $defines(container, {
509
- value: {
510
- get() {
511
- return Array.from(selectedValues);
512
- },
513
- set(newValues) {
514
- selectedValues = new Set(newValues);
515
- for (let i = 0; i < checkboxes.length; i++) {
516
- const checkbox = checkboxes[i];
517
- checkbox.checked = selectedValues.has(checkbox.value);
518
- }
519
- },
520
- },
521
- disabled: {
522
- get() {
523
- return checkboxes.map((cb) => cb.disabled);
524
- },
525
- set(newDisabled) {
526
- for (let i = 0; i < checkboxes.length; i++) {
527
- const checkbox = checkboxes[i];
528
- checkbox.disabled = Boolean(newDisabled);
529
- }
530
- },
531
- },
532
- disableAll: {
533
- value: () => {
534
- for (let i = 0; i < checkboxes.length; i++) {
535
- checkboxes[i].disabled = true;
536
- }
537
- },
538
- },
539
- enableAll: {
540
- value: () => {
541
- for (let i = 0; i < checkboxes.length; i++) {
542
- checkboxes[i].disabled = false;
543
- }
544
- },
545
- },
815
+ const modelRef = $modelOrRef(props, props.checked ?? false);
816
+ modelRef.addOnChange((newValue) => {
817
+ inputEl.checked = newValue;
818
+ toggleIcon(newValue, interminateRef.value);
546
819
  });
820
+ const inputEl = (jsx("input", { type: "checkbox", class: "mui-checkbox-input", checked: modelRef.value, value: valueRef, disabled: disabledRef, "on:change": handleChange }));
821
+ const uncheckedIcon = createUncheckedIcon();
822
+ const checkedIcon = createCheckedIcon();
823
+ const indeterminateIcon = createIndeterminateIcon();
824
+ // Initialize icon state
825
+ toggleIcon(modelRef.value, interminateRef.value);
826
+ const classRef = computed(() => {
827
+ return `mui-checkbox-wrapper mui-checkbox-size-${sizeRef.value} ${disabledRef.value ? 'mui-checkbox-disabled' : ''} mui-checkbox-color-${colorRef.value}`;
828
+ }, [colorRef, disabledRef, sizeRef]);
829
+ const container = (jsxs("label", { class: classRef, children: [inputEl, jsxs("span", { class: "mui-checkbox-icon", children: [uncheckedIcon, checkedIcon, indeterminateIcon] }), jsx("span", { "k-if": labelRef, class: "mui-checkbox-label", children: labelRef })] }));
830
+ return container;
831
+ }
832
+ /**
833
+ * CheckboxGroup component - groups multiple checkboxes together
834
+ */
835
+ function CheckboxGroup(props) {
836
+ let { row = false, 'on:change': onChange = $emptyFn } = props;
837
+ const changeHandler = (checked, checkboxValue) => {
838
+ if (checked) {
839
+ $ArrayPushUnique(modelRef.value, checkboxValue);
840
+ }
841
+ else {
842
+ $ArrayDelete(modelRef.value, checkboxValue);
843
+ }
844
+ onChange(modelRef.value);
845
+ };
846
+ const rowRef = toReactive(props.row ?? true);
847
+ const sizeRef = toReactive(props.size ?? 'medium');
848
+ const modelRef = $modelOrRef(props, props.value ?? []);
849
+ modelRef.addOnChange((newValues) => {
850
+ for (let i = 0; i < checkboxes.value.length; i++) {
851
+ const c = checkboxes.value[i];
852
+ c.checked = newValues.includes(c.value);
853
+ }
854
+ });
855
+ const customClassRef = toReactive(props.class ?? '');
856
+ const classRef = computed(() => {
857
+ return `mui-checkbox-group ${rowRef.value ? 'mui-checkbox-group-row' : ''} ${customClassRef.value}`;
858
+ }, [rowRef, customClassRef]);
859
+ const styleRef = toReactive(parseStyle(props.style ?? ''));
860
+ const optionsRef = toReactive(props.options);
861
+ const checkboxes = computed(() => {
862
+ return optionsRef.value.map((o) => {
863
+ o.size = sizeRef.value;
864
+ o.checked = modelRef.value.includes(o.value);
865
+ const originalChange = o['on:change'];
866
+ if (originalChange) {
867
+ if (typeof originalChange !== 'function') {
868
+ throw new Error('[kt.js error] CheckboxGroup: handler must be a function');
869
+ }
870
+ o['on:change'] = (checked, value) => {
871
+ originalChange(checked, value);
872
+ changeHandler(checked, value);
873
+ };
874
+ }
875
+ else {
876
+ o['on:change'] = changeHandler;
877
+ }
878
+ return Checkbox(o);
879
+ });
880
+ }, [optionsRef, sizeRef]);
881
+ console.log('checkboxes', checkboxes.value);
882
+ const container = (jsx("div", { class: classRef, style: styleRef, role: "group", children: checkboxes }));
547
883
  return container;
548
884
  }
549
885
 
@@ -552,11 +888,30 @@ function CheckboxGroup(props) {
552
888
  * Only handles open/close state, title and content are passed as props
553
889
  */
554
890
  function Dialog(props) {
555
- let { open = false, 'kt:close': onClose = $emptyFn, title, children, actions, maxWidth = 'sm', fullWidth = false, } = props;
891
+ let { 'on:close': onClose = $emptyFn, children, actions } = props;
892
+ const titleRef = toReactive(props.title ?? '');
893
+ const openRef = toReactive(props.open ?? false, (isOpen) => {
894
+ if (isOpen) {
895
+ // Opening: set display first, then add class with double RAF for animation
896
+ container.style.display = 'flex';
897
+ setTimeout(() => container.classList.add('kt-dialog-backdrop-open'), 50);
898
+ }
899
+ else {
900
+ container.classList.remove('kt-dialog-backdrop-open');
901
+ setTimeout(() => {
902
+ if (!openRef.value) {
903
+ container.style.display = 'none';
904
+ }
905
+ }, 225);
906
+ }
907
+ });
908
+ const sizeRef = toReactive(props.size ?? 'sm');
909
+ const fullWidthRef = toReactive(props.fullWidth ?? false);
910
+ const classNameRef = computed(() => `kt-dialog-paper ${sizeRef.value ? `kt-dialog-maxWidth-${sizeRef.value}` : ''} ${fullWidthRef.value ? 'kt-dialog-fullWidth' : ''}`, [sizeRef, fullWidthRef]);
556
911
  // Handle ESC key - store handler for cleanup
557
912
  const keyDownHandler = (e) => {
558
913
  if (e.key === 'Escape') {
559
- open = false;
914
+ openRef.value = false;
560
915
  onClose();
561
916
  }
562
917
  };
@@ -566,7 +921,7 @@ function Dialog(props) {
566
921
  }
567
922
  };
568
923
  // Backdrop element
569
- const container = (jsx("div", { class: `kt-dialog-backdrop ${open ? 'kt-dialog-backdrop-open' : ''}`, "on:click": handleBackdropClick, style: { display: open ? 'flex' : 'none' }, children: jsxs("div", { class: `kt-dialog-paper ${maxWidth ? `kt-dialog-maxWidth-${maxWidth}` : ''} ${fullWidth ? 'kt-dialog-fullWidth' : ''}`, "on:click": (e) => e.stopPropagation(), children: [jsx("div", { "k-if": title, class: "kt-dialog-title", children: jsx("h2", { children: title }) }), jsx("div", { "k-if": children, class: "kt-dialog-content", children: children }), jsx("div", { "k-if": actions, class: "kt-dialog-actions", children: actions })] }) }));
924
+ const container = (jsx("div", { class: `kt-dialog-backdrop ${openRef.value ? 'kt-dialog-backdrop-open' : ''}`, style: { display: openRef.value ? 'flex' : 'none' }, "on:click": handleBackdropClick, children: jsxs("div", { class: classNameRef, "on:click": (e) => e.stopPropagation(), children: [jsx("div", { "k-if": titleRef, class: "kt-dialog-title", children: jsx("h2", { children: titleRef }) }), jsx("div", { "k-if": children, class: "kt-dialog-content", children: children }), jsx("div", { "k-if": actions, class: "kt-dialog-actions", children: actions })] }) }));
570
925
  document.addEventListener('keydown', keyDownHandler);
571
926
  // Store cleanup function
572
927
  const originalRemove = container.remove;
@@ -576,30 +931,6 @@ function Dialog(props) {
576
931
  }
577
932
  return originalRemove.call(container);
578
933
  };
579
- $defines(container, {
580
- open: {
581
- get: () => open,
582
- set: (isOpen) => {
583
- if (isOpen === open) {
584
- return;
585
- }
586
- open = isOpen;
587
- if (isOpen) {
588
- // Opening: set display first, then add class with double RAF for animation
589
- container.style.display = 'flex';
590
- setTimeout(() => container.classList.add('kt-dialog-backdrop-open'), 50);
591
- }
592
- else {
593
- container.classList.remove('kt-dialog-backdrop-open');
594
- setTimeout(() => {
595
- if (!open) {
596
- container.style.display = 'none';
597
- }
598
- }, 225);
599
- }
600
- },
601
- },
602
- });
603
934
  return container;
604
935
  }
605
936
 
@@ -627,192 +958,68 @@ function FormLabel(props) {
627
958
  return element;
628
959
  }
629
960
 
630
- // Cached native methods for performance optimization
631
- const { get: $buttonDisabledGetter, set: $buttonDisabledSetter } = Object.getOwnPropertyDescriptor(HTMLButtonElement.prototype, 'disabled');
632
-
633
- // Shared utilities and cached native methods for kt.js framework
634
- // Re-export all utilities
635
- Object.defineProperty(window, '@ktjs/shared', { value: '0.20.0' });
636
-
637
- document.createElement('div');
638
-
639
- class KTRef {
640
- /**
641
- * Indicates that this is a KTRef instance
642
- */
643
- isKT = true;
644
- /**
645
- * @internal
646
- */
647
- _value;
648
- /**
649
- * @internal
650
- */
651
- _onChanges;
652
- constructor(_value, _onChanges) {
653
- this._value = _value;
654
- this._onChanges = _onChanges;
655
- }
656
- /**
657
- * If new value and old value are both nodes, the old one will be replaced in the DOM
658
- */
659
- get value() {
660
- return this._value;
661
- }
662
- set value(newValue) {
663
- if (newValue === this._value) {
664
- return;
665
- }
666
- // replace the old node with the new one in the DOM if both are nodes
667
- if (this._value instanceof Node && newValue instanceof Node) {
668
- if (newValue.contains(this._value)) {
669
- this._value.remove();
670
- }
671
- this._value.replaceWith(newValue);
672
- }
673
- const oldValue = this._value;
674
- this._value = newValue;
675
- for (let i = 0; i < this._onChanges.length; i++) {
676
- this._onChanges[i](newValue, oldValue);
677
- }
678
- }
679
- addOnChange(callback) {
680
- this._onChanges.push(callback);
681
- }
682
- removeOnChange(callback) {
683
- for (let i = this._onChanges.length - 1; i >= 0; i--) {
684
- if (this._onChanges[i] === callback) {
685
- this._onChanges.splice(i, 1);
686
- return true;
687
- }
688
- }
689
- return false;
690
- }
691
- }
692
- /**
693
- * Reference to the created HTML element.
694
- * - can alse be used to store normal values, but it is not reactive.
695
- * @param value mostly an HTMLElement
696
- */
697
- function ref(value, onChange) {
698
- return new KTRef(value, []);
699
- }
700
- /**
701
- * A helper to create redrawable elements
702
- * ```tsx
703
- * export function MyComponent() {
704
- * let aa = 10;
705
- * // ...
706
- * // aa might be changed
707
- * return createRedrawable(() => <div>{aa}</div>);
708
- * }
709
- * ```
710
- * Then the returned element has a `redraw` method to redraw itself with new values.
711
- * @param creator a simple creator function that returns an element
712
- * @returns created element's ref
713
- */
714
- function createRedrawable(creator) {
715
- const elRef = ref();
716
- const redraw = () => {
717
- elRef.value = creator(); // ref setter automatically calls replaceWith
718
- elRef.redraw = redraw;
719
- return elRef.value;
720
- };
721
- redraw();
722
- return elRef;
723
- }
724
-
725
- /**
726
- * LinearProgress component - mimics MUI LinearProgress appearance and behavior
727
- */
728
961
  function LinearProgress(props) {
729
- let { variant = 'indeterminate', progress: value = 0, color = 'primary' } = props;
730
- const classes = [
731
- 'mui-linear-progress',
732
- `mui-linear-progress-${color}`,
733
- `mui-linear-progress-${variant}`,
734
- props.class ? props.class : '',
735
- ].join(' ');
736
- const styleString = parseStyle(props.style);
737
- // Calculate progress percentage
738
- let progressValue = Math.min(Math.max(value, 0), 100);
739
- const barRef = ref();
740
- const container = (jsx("div", { class: classes, style: styleString, role: "progressbar", "aria-valuenow": progressValue, children: jsx("div", { ref: barRef, class: "mui-linear-progress-bar", style: variant === 'determinate' ? `width: ${progressValue}%` : '' }) }));
741
- $defines(container, {
742
- progress: {
743
- get() {
744
- return progressValue;
745
- },
746
- set(newValue) {
747
- progressValue = Math.min(Math.max(newValue, 0), 100);
748
- if (variant === 'determinate') {
749
- barRef.value.style.width = `${progressValue}%`;
750
- }
751
- },
752
- },
753
- });
962
+ const valueRef = ref(props.progress ?? 0);
963
+ const customClassRef = toReactive(props.class ?? '');
964
+ const colorRef = toReactive(props.color ?? 'primary');
965
+ const variantRef = toReactive(props.variant ?? 'indeterminate');
966
+ const classRef = computed(() => {
967
+ return `mui-linear-progress mui-linear-progress-${variantRef.value} mui-linear-progress-${colorRef.value} ${customClassRef.value}`;
968
+ }, [customClassRef, colorRef, variantRef]);
969
+ const styleRef = toReactive(props.style ?? '');
970
+ const barLengthRef = computed(() => {
971
+ return variantRef.value === 'determinate' ? `width: ${valueRef.value}%` : '';
972
+ }, [variantRef, valueRef]);
973
+ const container = (jsx("div", { class: classRef, style: styleRef, role: "progressbar", "aria-valuenow": valueRef, children: jsx("div", { class: "mui-linear-progress-bar", style: barLengthRef }) }));
754
974
  return container;
755
975
  }
756
976
 
757
- /**
758
- * TextField component - mimics MUI TextField appearance and behavior
759
- */
760
977
  function TextField(props) {
761
- let { label = '', placeholder = '', value = '', type = 'text', disabled = false, readonly = false, required = false, error = false, helperText = '', fullWidth = false, multiline = false, rows = 3, size = 'medium', } = props;
762
- const onInput = generateHandler(props, 'kt:input');
763
- const onInputTrim = generateHandler(props, 'kt-trim:input');
764
- const onChange = generateHandler(props, 'kt:change');
765
- const onChangeTrim = generateHandler(props, 'kt-trim:change');
766
- const onBlur = generateHandler(props, 'kt:blur');
767
- const onFocus = generateHandler(props, 'kt:focus');
768
- const updateContainerClass = () => {
769
- container.className = [
770
- 'mui-textfield-root',
771
- `mui-textfield-size-${size}`,
772
- isFocused ? 'mui-textfield-focused' : '',
773
- error ? 'mui-textfield-error' : '',
774
- disabled ? 'mui-textfield-disabled' : '',
775
- fullWidth ? 'mui-textfield-fullwidth' : '',
776
- label && inputEl.value ? 'mui-textfield-has-value' : '',
777
- label ? '' : 'mui-textfield-no-label',
778
- ].join(' ');
779
- };
978
+ // # events
979
+ const onInput = props['on:input'] ?? $emptyFn;
980
+ const onInputTrim = props['on-trim:input'] ?? $emptyFn;
981
+ const onChange = props['on:change'] ?? $emptyFn;
982
+ const onChangeTrim = props['on-trim:change'] ?? $emptyFn;
983
+ const onBlur = props['on:blur'] ?? $emptyFn;
984
+ const onFocus = props['on:focus'] ?? $emptyFn;
985
+ const isFocusedRef = ref(false);
986
+ // # methods
780
987
  const handleInput = () => {
781
- updateContainerClass();
782
- if (type === 'number') {
988
+ if (inputType === 'number') {
783
989
  const v = Number(inputEl.value);
990
+ modelRef.value = v;
784
991
  onInput(v);
785
992
  onInputTrim(v);
786
993
  }
787
994
  else {
995
+ modelRef.value = inputEl.value;
788
996
  onInput(inputEl.value);
789
997
  onInputTrim(inputEl.value.trim());
790
998
  }
791
999
  };
792
1000
  const handleChange = () => {
793
- updateContainerClass();
794
- if (type === 'number') {
1001
+ if (inputType === 'number') {
795
1002
  const v = Number(inputEl.value);
1003
+ modelRef.value = v;
796
1004
  onChange(v);
797
1005
  onChangeTrim(v);
798
1006
  }
799
1007
  else {
1008
+ modelRef.value = inputEl.value;
800
1009
  onChange(inputEl.value);
801
1010
  onChangeTrim(inputEl.value.trim());
802
1011
  }
803
1012
  };
804
1013
  const handleFocus = () => {
805
- isFocused = true;
806
- updateContainerClass();
1014
+ isFocused.value = true;
807
1015
  onFocus(inputEl.value);
808
1016
  };
809
1017
  const handleBlur = () => {
810
- isFocused = false;
811
- updateContainerClass();
1018
+ isFocused.value = false;
812
1019
  onBlur(inputEl.value);
813
1020
  };
814
1021
  const handleWrapperMouseDown = (e) => {
815
- if (disabled) {
1022
+ if (disabledRef.value) {
816
1023
  return;
817
1024
  }
818
1025
  const target = e.target;
@@ -821,92 +1028,55 @@ function TextField(props) {
821
1028
  }
822
1029
  setTimeout(() => inputEl.focus(), 0);
823
1030
  };
824
- const getPlaceholder = () => (label && !isFocused && !value ? '' : placeholder);
825
- let isFocused = false;
1031
+ const getPlaceholder = () => (labelRef.value && !isFocused && !modelRef.value ? '' : placeholderRef.value);
1032
+ // # non-refs
1033
+ const inputType = deref(props.type ?? 'text');
1034
+ const multiline = props.multiline;
1035
+ // # refs
1036
+ // Create refs for all reactive properties
1037
+ const labelRef = toReactive(props.label ?? '');
1038
+ const placeholderRef = toReactive(props.placeholder ?? '', () => (inputEl.placeholder = getPlaceholder()));
1039
+ const disabledRef = toReactive(props.disabled ?? false);
1040
+ const readOnlyRef = toReactive(props.readOnly ?? false);
1041
+ const requiredRef = toReactive(props.required ?? false);
1042
+ const errorRef = toReactive(props.error ?? false);
1043
+ const helperTextRef = toReactive(props.helperText ?? '');
1044
+ const fullWidthRef = toReactive(props.fullWidth ?? false);
1045
+ const rowsRef = toReactive(props.rows ?? 3);
1046
+ const sizeRef = toReactive(props.size ?? 'medium');
1047
+ // k-model takes precedence over value prop for two-way binding
1048
+ const modelRef = $modelOrRef(props, props.value ?? '');
1049
+ // Add change listeners for reactive properties
1050
+ // `k-if` changing triggers redrawing, no need to do this again
1051
+ // // labelRef.addOnChange(() => {
1052
+ // // wrapperRef.redraw();
1053
+ // // updateContainerClass();
1054
+ // // });
1055
+ const isFocused = ref(false);
826
1056
  const inputEl = multiline
827
- ? (jsx("textarea", { class: "mui-textfield-input", placeholder: getPlaceholder(), value: value, disabled: disabled, readOnly: readonly, required: required, rows: rows, "on:input": handleInput, "on:change": handleChange, "on:focus": handleFocus, "on:blur": handleBlur }))
828
- : (jsx("input", { type: type, class: "mui-textfield-input", placeholder: getPlaceholder(), value: value, disabled: disabled, readOnly: readonly, required: required, "on:input": handleInput, "on:change": handleChange, "on:focus": handleFocus, "on:blur": handleBlur }));
829
- const helperTextEl = jsx("p", { class: "mui-textfield-helper-text", children: helperText });
830
- const wrapperRef = createRedrawable(() => (jsxs("div", { class: "mui-textfield-wrapper", "on:mousedown": handleWrapperMouseDown, children: [jsxs("label", { "k-if": label, class: "mui-textfield-label", children: [label, required && jsx("span", { class: "mui-textfield-required", children: "*" })] }), jsx("div", { class: "mui-textfield-input-wrapper", children: inputEl }), jsx("fieldset", { class: "mui-textfield-fieldset", children: jsx("legend", { "k-if": label, class: "mui-textfield-legend", children: jsxs("span", { children: [label, required && '*'] }) }) })] })));
831
- const container = (jsxs("div", { class: 'mui-textfield-root ' + (props.class ? props.class : ''), style: parseStyle(props.style), children: [wrapperRef, helperTextEl] }));
832
- // Initialize classes
833
- setTimeout(() => updateContainerClass(), 0);
834
- $defines(container, {
835
- value: {
836
- get() {
837
- return inputEl.value;
838
- },
839
- set(newValue) {
840
- inputEl.value = newValue;
841
- updateContainerClass();
842
- },
843
- },
844
- label: {
845
- get() {
846
- return label;
847
- },
848
- set(newLabel) {
849
- label = newLabel;
850
- wrapperRef.redraw(); // label takes too much and should be redrawn
851
- updateContainerClass();
852
- },
853
- },
854
- placeholder: {
855
- get() {
856
- return placeholder;
857
- },
858
- set(newPlaceholder) {
859
- placeholder = newPlaceholder;
860
- inputEl.placeholder = getPlaceholder();
861
- },
862
- },
863
- type: {
864
- get() {
865
- return type;
866
- },
867
- set(newType) {
868
- type = newType || 'text';
869
- inputEl.type = type;
870
- },
871
- },
872
- disabled: {
873
- get() {
874
- return disabled;
875
- },
876
- set(val) {
877
- disabled = !!val;
878
- inputEl.disabled = disabled;
879
- container.classList.toggle('mui-textfield-disabled', disabled);
880
- },
881
- },
882
- readonly: {
883
- get() {
884
- return readonly;
885
- },
886
- set(val) {
887
- readonly = Boolean(val);
888
- inputEl.readOnly = readonly;
889
- },
890
- },
891
- error: {
892
- get() {
893
- return error;
894
- },
895
- set(val) {
896
- error = Boolean(val);
897
- container.classList.toggle('mui-textfield-error', error);
898
- },
899
- },
900
- helperText: {
901
- get() {
902
- return helperText;
903
- },
904
- set(text) {
905
- helperTextEl.textContent = text;
906
- helperTextEl.style.display = text ? 'block' : 'none';
907
- },
908
- },
909
- });
1057
+ ? (jsx("textarea", { class: "mui-textfield-input", placeholder: getPlaceholder(), value: modelRef.value, disabled: disabledRef, readOnly: readOnlyRef, required: requiredRef, rows: rowsRef, "on:input": handleInput, "on:change": handleChange, "on:focus": handleFocus, "on:blur": handleBlur }))
1058
+ : (jsx("input", { type: inputType, class: "mui-textfield-input", placeholder: getPlaceholder(), value: modelRef.value, disabled: disabledRef, readOnly: readOnlyRef, required: requiredRef, "on:input": handleInput, "on:change": handleChange, "on:focus": handleFocus, "on:blur": handleBlur }));
1059
+ modelRef.addOnChange((newValue) => (inputEl.value = newValue));
1060
+ const styleRef = toReactive(parseStyle(props.style ?? ''));
1061
+ const customClassRef = toReactive(props.class ?? '');
1062
+ const classRef = computed(() => {
1063
+ const className = [
1064
+ 'mui-textfield-root',
1065
+ `mui-textfield-size-${sizeRef.value}`,
1066
+ isFocusedRef.value ? 'mui-textfield-focused' : '',
1067
+ errorRef.value ? 'mui-textfield-error' : '',
1068
+ disabledRef.value ? 'mui-textfield-disabled' : '',
1069
+ fullWidthRef.value ? 'mui-textfield-fullwidth' : '',
1070
+ labelRef.value && inputEl.value ? 'mui-textfield-has-value' : '',
1071
+ labelRef.value ? '' : 'mui-textfield-no-label',
1072
+ customClassRef.value ? customClassRef.value : '',
1073
+ ].join(' ');
1074
+ return className;
1075
+ }, [sizeRef, errorRef, disabledRef, fullWidthRef, labelRef, isFocusedRef, customClassRef]);
1076
+ // if (multiline) {
1077
+ // rowsRef.addOnChange((newRows) => ((inputEl as HTMLTextAreaElement).rows = newRows));
1078
+ // }
1079
+ const container = (jsxs("div", { class: classRef, style: styleRef, children: [jsxs("div", { class: "mui-textfield-wrapper", "on:mousedown": handleWrapperMouseDown, children: [jsxs("label", { class: "mui-textfield-label", children: [labelRef, jsx("span", { "k-if": requiredRef, class: "mui-textfield-required", children: "*" })] }), jsx("div", { class: "mui-textfield-input-wrapper", children: inputEl }), jsx("fieldset", { class: "mui-textfield-fieldset", children: jsx("legend", { class: "mui-textfield-legend", children: jsxs("span", { children: [labelRef, jsx("span", { "k-if": requiredRef, children: "*" })] }) }) })] }), jsx("p", { class: "mui-textfield-helper-text", children: helperTextRef })] }));
910
1080
  return container;
911
1081
  }
912
1082
 
@@ -914,7 +1084,7 @@ function TextField(props) {
914
1084
  * Radio component - mimics MUI Radio appearance and behavior
915
1085
  */
916
1086
  function Radio(props) {
917
- const onChange = generateHandler(props, 'kt:change');
1087
+ const onChange = props['on:change'] ?? $emptyFn;
918
1088
  const toggleIcon = (checked) => {
919
1089
  uncheckedIcon.style.display = checked ? 'none' : '';
920
1090
  checkedIcon.style.display = checked ? '' : 'none';
@@ -929,7 +1099,9 @@ function Radio(props) {
929
1099
  onChange(checked, value);
930
1100
  };
931
1101
  let { checked = false, value = '', label: text = '', size = 'small', disabled = false, color = 'primary' } = props;
932
- const input = (jsx("input", { type: "radio", class: "mui-radio-input", checked: checked, value: value, disabled: disabled, "on:change": handleChange }));
1102
+ const valueRef = toReactive(props.value ?? '');
1103
+ const disabledRef = toReactive(props.disabled ?? false);
1104
+ const input = (jsx("input", { type: "radio", class: "mui-radio-input", checked: checked, value: valueRef, disabled: disabledRef, "on:change": handleChange }));
933
1105
  const uncheckedIcon = (jsx("span", { class: "mui-radio-icon-unchecked", children: jsx("svg", { viewBox: "0 0 24 24", children: jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" }) }) }));
934
1106
  const checkedIcon = (jsx("span", { class: "mui-radio-icon-checked", children: jsx("svg", { viewBox: "0 0 24 24", children: jsx("path", { d: "M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" }) }) }));
935
1107
  // initialize icon state
@@ -959,7 +1131,7 @@ function Radio(props) {
959
1131
  */
960
1132
  function RadioGroup(props) {
961
1133
  let { value = '', size = 'small', row = false } = props;
962
- const onChange = generateHandler(props, 'kt:change');
1134
+ const onChange = props['on:change'] ?? $emptyFn;
963
1135
  const changeHandler = (checked, value) => {
964
1136
  if (checked) {
965
1137
  onChange(value);
@@ -969,15 +1141,15 @@ function RadioGroup(props) {
969
1141
  const radios = props.options.map((o) => {
970
1142
  o.size = size;
971
1143
  o.checked = value === o.value;
972
- const originalChange = o['kt:change'];
1144
+ const originalChange = o['on:change'];
973
1145
  if (originalChange) {
974
- o['kt:change'] = (checked, newValue) => {
1146
+ o['on:change'] = (checked, newValue) => {
975
1147
  originalChange(checked, newValue);
976
1148
  changeHandler(checked, newValue);
977
1149
  };
978
1150
  }
979
1151
  else {
980
- o['kt:change'] = changeHandler;
1152
+ o['on:change'] = changeHandler;
981
1153
  }
982
1154
  return Radio(o);
983
1155
  });
@@ -1000,22 +1172,10 @@ function RadioGroup(props) {
1000
1172
  * Select component - mimics MUI Select appearance and behavior
1001
1173
  */
1002
1174
  function Select(props) {
1003
- let { value = '', options = [], label = '', placeholder = '', size = 'medium', fullWidth = false, disabled = false, } = props;
1004
- const onChange = generateHandler(props, 'kt:change');
1005
- let isOpen = false;
1006
- let isFocused = false;
1007
- const selectRef = ref();
1008
- const selectLabelRef = ref();
1009
- // Toggle dropdown
1010
- const toggleMenu = () => {
1011
- if (disabled) {
1012
- return;
1013
- }
1014
- isOpen = !isOpen;
1015
- updateMenu();
1016
- };
1017
- // Update menu visibility
1018
- const updateMenu = () => {
1175
+ const onChange = props['on:change'] ?? $emptyFn;
1176
+ // # refs
1177
+ const isFocusedRef = ref(false);
1178
+ const open = ref(false, (isOpen) => {
1019
1179
  if (isOpen) {
1020
1180
  menu.value.style.display = 'block';
1021
1181
  void menu.value.offsetHeight; // & Trigger reflow to enable animation
@@ -1029,85 +1189,139 @@ function Select(props) {
1029
1189
  }, 200);
1030
1190
  }
1031
1191
  menu.value.classList.toggle('mui-select-menu-open', isOpen);
1032
- selectRef.value.classList.toggle('mui-select-open', isOpen);
1192
+ container.classList.toggle('mui-select-open', isOpen);
1193
+ });
1194
+ // # ref props
1195
+ const placeholderRef = toReactive(props.placeholder ?? '');
1196
+ const labelRef = toReactive(props.label ?? '');
1197
+ const optionsRef = toReactive(props.options, (newOptions) => {
1198
+ if (!newOptions.find((o) => o.value === modelRef.value)) {
1199
+ onChange((modelRef.value = ''));
1200
+ }
1201
+ });
1202
+ const disabledRef = toReactive(props.disabled ?? false, (v) => container.classList.toggle('mui-select-disabled', v));
1203
+ const modelRef = $modelOrRef(props, props.value ?? '');
1204
+ const styleRef = toReactive(parseStyle(props.style ?? ''));
1205
+ const classRef = toReactive(props.class ?? '');
1206
+ const sizeRef = toReactive(props.size ?? 'medium');
1207
+ const fullwidthRef = toReactive(props.fullWidth ?? false);
1208
+ const className = computed(() => {
1209
+ return `mui-select-wrapper mui-select-size-${sizeRef.value} ${fullwidthRef.value ? 'mui-select-fullWidth' : ''} ${classRef.value} ${disabledRef.value ? 'mui-select-disabled' : ''}`;
1210
+ }, [sizeRef, fullwidthRef, classRef, disabledRef]);
1211
+ const label = computed(() => {
1212
+ if (labelRef.value) {
1213
+ return (jsx("label", { class: `mui-select-label ${modelRef.value || isFocusedRef.value || placeholderRef.value ? 'focused' : ''}`, children: labelRef }));
1214
+ }
1215
+ return '';
1216
+ }, [labelRef, modelRef, isFocusedRef, placeholderRef]);
1217
+ // Toggle dropdown
1218
+ const toggleMenu = () => {
1219
+ if (!disabledRef.value) {
1220
+ open.value = !open.value;
1221
+ }
1033
1222
  };
1034
1223
  // Handle option click
1035
- const handleOptionClick = (newValue) => {
1036
- value = newValue;
1037
- isOpen = false;
1038
- onChange(value);
1039
- updateMenu();
1040
- updateLabelState();
1041
- valueDisplay.redraw();
1042
- setTimeout(() => menu.redraw(), 200);
1224
+ const handleOptionClick = (e) => {
1225
+ const newValue = e.currentTarget.dataset.value;
1226
+ modelRef.value = newValue;
1227
+ onChange(newValue);
1228
+ open.value = false;
1043
1229
  };
1044
1230
  // Close menu when clicking outside
1045
1231
  const handleClickOutside = (e) => {
1046
- if (!selectRef.value.contains(e.target)) {
1047
- isOpen = false;
1048
- updateMenu();
1232
+ if (!container.contains(e.target)) {
1233
+ open.value = false;
1049
1234
  }
1050
1235
  };
1051
1236
  // Handle focus
1052
- const handleFocus = () => {
1053
- isFocused = true;
1054
- updateLabelState();
1055
- };
1056
- const handleBlur = () => {
1057
- isFocused = false;
1058
- updateLabelState();
1059
- };
1060
- // Update label focused state
1061
- const updateLabelState = () => {
1062
- selectLabelRef.value.classList?.toggle('focused', isFocused || !!value);
1063
- };
1064
- const valueDisplay = createRedrawable(() => {
1065
- const o = options.find((opt) => opt.value === value);
1066
- let inner;
1067
- if (o === undefined) {
1068
- inner = jsx("span", { class: "mui-select-placeholder", children: placeholder || '\u00a0' });
1069
- }
1070
- else {
1071
- inner = o.label;
1072
- }
1073
- return jsx("div", { class: "mui-select-display", children: inner });
1074
- });
1075
- const menu = createRedrawable(() => {
1076
- return (jsx("div", { class: "mui-select-menu", style: "display: none;", children: options.map((option) => (jsx("div", { class: `mui-select-option ${option.value === value ? 'selected' : ''}`, "on:click": () => handleOptionClick(option.value), children: option.label }))) }));
1077
- });
1237
+ const handleFocus = () => (isFocusedRef.value = true);
1238
+ const handleBlur = () => (isFocusedRef.value = false);
1239
+ const defaultEmpty = jsx("span", { class: "mui-select-placeholder", children: placeholderRef.value || '\u00a0' });
1240
+ const displayedValue = computed(() => {
1241
+ const o = optionsRef.value.find((item) => item.value === modelRef.value);
1242
+ return jsx("div", { class: "mui-select-display", children: o?.label ?? defaultEmpty });
1243
+ }, [modelRef]);
1244
+ const menu = computed(() => {
1245
+ return (jsxs("div", { class: "mui-select-menu", style: "display: none;", children: [optionsRef.value.map((o) => (jsx("div", { class: `mui-select-option ${o.value === modelRef.value ? 'selected' : ''}`, "data-value": o.value, "on:click": handleOptionClick, children: o.label }))), ' '] }));
1246
+ }, [optionsRef, modelRef]);
1078
1247
  // Create container
1079
- const container = (jsxs("div", { ref: selectRef, class: `mui-select-wrapper mui-select-size-${size} ${props.class ?? ''} ${fullWidth ? 'mui-select-fullWidth' : ''} ${disabled ? 'mui-select-disabled' : ''}`, style: parseStyle(props.style), children: [jsx("label", { "k-if": label, ref: selectLabelRef, class: `mui-select-label ${value || isFocused ? 'focused' : ''}`, children: label }), jsxs("div", { class: "mui-select-control mui-select-outlined", "on:click": toggleMenu, "on:focus": handleFocus, "on:blur": handleBlur, tabIndex: disabled ? -1 : 0, children: [valueDisplay, jsx("input", { type: "hidden", value: value }), jsx("fieldset", { class: "mui-select-fieldset", children: jsx("legend", { children: jsx("span", { children: label }) }) }), jsx("svg", { class: "mui-select-icon", focusable: "false", "aria-hidden": "true", viewBox: "0 0 24 24", width: "24", height: "24", children: jsx("path", { d: "M7 10l5 5 5-5Z", fill: "currentColor" }) })] }), menu] }));
1080
- $defines(container, {
1081
- value: {
1082
- get: () => value,
1083
- set: (newValue) => {
1084
- value = newValue;
1085
- updateLabelState();
1086
- valueDisplay.redraw();
1087
- menu.redraw();
1088
- },
1089
- },
1090
- disabled: {
1091
- get: () => disabled,
1092
- set: (newDisabled) => {
1093
- disabled = newDisabled;
1094
- if (disabled) {
1095
- isOpen = false;
1096
- updateMenu();
1097
- }
1098
- container.classList.toggle('mui-select-disabled', disabled);
1099
- },
1100
- },
1101
- });
1248
+ const container = (jsxs("div", { class: className, style: styleRef, children: [label, jsxs("div", { class: "mui-select-control mui-select-outlined", "on:click": toggleMenu, "on:focus": handleFocus, "on:blur": handleBlur, tabIndex: disabledRef.value ? -1 : 0, children: [displayedValue, jsx("input", { type: "hidden", value: modelRef }), jsx("fieldset", { class: "mui-select-fieldset", children: jsx("legend", { class: "mui-select-legend", children: jsx("span", { children: labelRef }) }) }), jsx("svg", { class: "mui-select-icon", focusable: "false", "aria-hidden": "true", viewBox: "0 0 24 24", width: "24", height: "24", children: jsx("path", { d: "M7 10l5 5 5-5Z", fill: "currentColor" }) })] }), menu] }));
1102
1249
  // Add global click listener
1103
1250
  setTimeout(() => {
1104
1251
  document.removeEventListener('click', handleClickOutside);
1105
1252
  document.addEventListener('click', handleClickOutside);
1106
- updateLabelState();
1107
1253
  }, 0);
1108
1254
  return container;
1109
1255
  }
1110
1256
 
1257
+ /**
1258
+ * Card component - mimics MUI Card appearance and behavior
1259
+ */
1260
+ function Card(props) {
1261
+ // # ref props
1262
+ const variantRef = toReactive(props.variant ?? 'elevation');
1263
+ const elevationRef = toReactive(props.elevation ?? 1);
1264
+ const squareRef = toReactive(props.square ?? false);
1265
+ const raisedRef = toReactive(props.raised ?? false);
1266
+ const styleRef = toReactive(parseStyle(props.style ?? ''));
1267
+ const classRef = toReactive(props.class ?? '');
1268
+ const className = computed(() => {
1269
+ const base = 'mui-card';
1270
+ const variantClass = `mui-card-${variantRef.value}`;
1271
+ const elevationClass = variantRef.value === 'elevation' ? `mui-card-elevation-${Math.min(24, Math.max(0, elevationRef.value))}` : '';
1272
+ const squareClass = squareRef.value ? 'mui-card-square' : '';
1273
+ const raisedClass = raisedRef.value ? 'mui-card-raised' : '';
1274
+ return `${base} ${variantClass} ${elevationClass} ${squareClass} ${raisedClass} ${classRef.value}`.trim().replace(/\s+/g, ' ');
1275
+ }, [variantRef, elevationRef, squareRef, raisedRef, classRef]);
1276
+ // Handle click
1277
+ const handleClick = props['on:click'] ?? (() => { });
1278
+ const container = (jsx("div", { class: className, style: styleRef, "on:click": handleClick, children: props.children }));
1279
+ return container;
1280
+ }
1281
+
1282
+ /**
1283
+ * Switch component - mimics MUI Switch appearance and behavior
1284
+ */
1285
+ function Switch(props) {
1286
+ const onChange = props['on:change'] ?? $emptyFn;
1287
+ // # ref props
1288
+ const labelRef = toReactive(props.label ?? '');
1289
+ const valueRef = toReactive(props.value ?? '');
1290
+ const colorRef = toReactive(props.color ?? 'primary');
1291
+ const sizeRef = toReactive(props.size ?? 'medium');
1292
+ const disabledRef = toReactive(props.disabled ?? false, (v) => {
1293
+ inputEl.disabled = v;
1294
+ container.classList.toggle('mui-switch-disabled', v);
1295
+ });
1296
+ const modelRef = $modelOrRef(props, props.checked ?? false);
1297
+ modelRef.addOnChange((newValue) => {
1298
+ inputEl.checked = newValue;
1299
+ track.classList.toggle('mui-switch-track-checked', newValue);
1300
+ thumb.classList.toggle('mui-switch-thumb-checked', newValue);
1301
+ });
1302
+ const styleRef = toReactive(parseStyle(props.style ?? ''));
1303
+ const classRef = toReactive(props.class ?? '');
1304
+ const className = computed(() => {
1305
+ return `mui-switch-wrapper mui-switch-size-${sizeRef.value} ${disabledRef.value ? 'mui-switch-disabled' : ''} mui-switch-color-${colorRef.value} ${classRef.value}`;
1306
+ }, [colorRef, disabledRef, sizeRef, classRef]);
1307
+ // Handle change
1308
+ const handleChange = () => {
1309
+ if (disabledRef.value) {
1310
+ return;
1311
+ }
1312
+ modelRef.value = inputEl.checked;
1313
+ onChange(modelRef.value, valueRef.value);
1314
+ };
1315
+ const inputEl = (jsx("input", { type: "checkbox", class: "mui-switch-input", checked: modelRef.value, value: valueRef, disabled: disabledRef, "on:change": handleChange }));
1316
+ const track = jsx("span", { class: "mui-switch-track" });
1317
+ const thumb = jsx("span", { class: "mui-switch-thumb" });
1318
+ const container = (jsxs("label", { class: className, style: styleRef, children: [inputEl, jsxs("span", { class: "mui-switch-base", children: [track, thumb] }), jsx("span", { "k-if": labelRef, class: "mui-switch-label", children: labelRef })] }));
1319
+ // Initialize state
1320
+ track.classList.toggle('mui-switch-track-checked', modelRef.value);
1321
+ thumb.classList.toggle('mui-switch-thumb-checked', modelRef.value);
1322
+ return container;
1323
+ }
1324
+
1111
1325
  function DownloadIcon(props) {
1112
1326
  return (jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "1em", height: "1em", ...props, children: jsx("path", { d: "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" }) }));
1113
1327
  }
@@ -1196,4 +1410,4 @@ function SelectAllIcon(props) {
1196
1410
  return (jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "1em", height: "1em", ...props, children: jsx("path", { d: "M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM7 17h10V7H7v10zm2-8h6v6H9V9z" }) }));
1197
1411
  }
1198
1412
 
1199
- export { Alert, Button, Checkbox, CheckboxGroup, ColorLensIcon, CompressIcon, ContentCopyIcon, ContentPasteIcon, DeleteIcon, Dialog, DownloadIcon, ExpandMoreIcon, FileOpenIcon, FolderOpenIcon, FormLabel, HomeIcon, LinearProgress, MenuIcon, NewReleasesIcon, PlayArrowIcon, QueuePlayNextIcon, Radio, RadioGroup, SaveIcon, Select, SelectAllIcon, SettingsIcon, StopIcon, SubtitlesIcon, TextField, UploadFileIcon, VideoFileIcon, WallpaperIcon };
1413
+ export { Alert, Button, Card, Checkbox, CheckboxGroup, ColorLensIcon, CompressIcon, ContentCopyIcon, ContentPasteIcon, DeleteIcon, Dialog, DownloadIcon, ExpandMoreIcon, FileOpenIcon, FolderOpenIcon, FormLabel, HomeIcon, LinearProgress, MenuIcon, NewReleasesIcon, PlayArrowIcon, QueuePlayNextIcon, Radio, RadioGroup, SaveIcon, Select, SelectAllIcon, SettingsIcon, StopIcon, SubtitlesIcon, Switch, TextField, UploadFileIcon, VideoFileIcon, WallpaperIcon };