@ktjs/mui 0.17.8 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -9,9 +9,9 @@ interface AlertProps {
9
9
  severity?: 'error' | 'warning' | 'info' | 'success';
10
10
  variant?: 'standard' | 'filled' | 'outlined';
11
11
  icon?: HTMLElement | KTHTMLElement | false;
12
- onClose?: () => void;
12
+ 'kt:close'?: () => void;
13
13
  }
14
- /**
14
+ /**s
15
15
  * Alert component - mimics MUI Alert appearance and behavior
16
16
  */
17
17
  declare function Alert(props: AlertProps): KTHTMLElement;
@@ -41,7 +41,7 @@ interface CheckboxProps {
41
41
  label?: string | KTHTMLElement | HTMLElement;
42
42
  checked?: boolean;
43
43
  size?: 'small' | 'medium';
44
- 'mui:change'?: (checked: boolean, value: string) => void;
44
+ 'kt:change'?: (checked: boolean, value: string) => void;
45
45
  disabled?: boolean;
46
46
  color?: 'primary' | 'secondary' | 'default' | 'success' | 'error' | 'warning';
47
47
  indeterminate?: boolean;
@@ -63,7 +63,7 @@ interface CheckboxGroupProps {
63
63
  value?: string[];
64
64
  size?: 'small' | 'medium';
65
65
  options: CheckboxProps[];
66
- 'mui:change'?: (values: string[]) => void;
66
+ 'kt:change'?: (values: string[]) => void;
67
67
  row?: boolean;
68
68
  }
69
69
  /**
@@ -73,7 +73,7 @@ declare function CheckboxGroup(props: CheckboxGroupProps): KTMuiCheckboxGroup;
73
73
 
74
74
  interface DialogProps {
75
75
  open?: boolean;
76
- 'mui:close'?: () => void;
76
+ 'kt:close'?: () => void;
77
77
  title?: string;
78
78
  children?: HTMLElement | HTMLElement[] | string;
79
79
  actions?: HTMLElement | HTMLElement[];
@@ -138,15 +138,24 @@ interface TextFieldProps {
138
138
  fullWidth?: boolean;
139
139
  multiline?: boolean;
140
140
  rows?: number;
141
- maxRows?: number;
142
141
  size?: 'small' | 'medium';
143
- 'mui:input'?: (value: string, event: Event) => void;
144
- 'mui:change'?: (value: string, event: Event) => void;
145
- 'mui:blur'?: (value: string, event: Event) => void;
146
- 'mui:focus'?: (value: string, event: Event) => void;
142
+ 'kt:input'?: (value: string, event: Event) => void;
143
+ 'kt-trim:input'?: (value: string, event: Event) => void;
144
+ 'kt:change'?: (value: string, event: Event) => void;
145
+ 'kt-trim:change'?: (value: string, event: Event) => void;
146
+ 'kt:blur'?: (value: string, event: Event) => void;
147
+ 'kt:focus'?: (value: string, event: Event) => void;
147
148
  }
148
149
  type KTMuiTextField = KTHTMLElement & {
149
150
  value: string;
151
+ label: string;
152
+ placeholder: string;
153
+ type: string;
154
+ disabled: boolean;
155
+ readonly: boolean;
156
+ required: boolean;
157
+ error: boolean;
158
+ helperText: string;
150
159
  };
151
160
  /**
152
161
  * TextField component - mimics MUI TextField appearance and behavior
@@ -158,7 +167,7 @@ interface RadioProps {
158
167
  text: string | KTHTMLElement | HTMLElement;
159
168
  checked?: boolean;
160
169
  size?: 'small' | 'medium';
161
- 'mui:change'?: (checked: boolean, value: string) => void;
170
+ 'kt:change'?: (checked: boolean, value: string) => void;
162
171
  disabled?: boolean;
163
172
  color?: 'primary' | 'secondary' | 'default';
164
173
  }
@@ -176,8 +185,8 @@ interface RadioGroupProps {
176
185
  name?: string;
177
186
  size?: 'small' | 'medium';
178
187
  options: RadioProps[];
179
- 'mui:change'?: (value: string) => void;
180
- 'mui:click'?: (checked: boolean) => void;
188
+ 'kt:change'?: (value: string) => void;
189
+ 'kt:click'?: (checked: boolean) => void;
181
190
  row?: boolean;
182
191
  }
183
192
  /**
@@ -197,7 +206,7 @@ interface SelectProps {
197
206
  options: SelectOption[];
198
207
  label?: string;
199
208
  placeholder?: string;
200
- 'mui:change'?: (value: string) => void;
209
+ 'kt:change'?: (value: string) => void;
201
210
  fullWidth?: boolean;
202
211
  disabled?: boolean;
203
212
  }
@@ -1,14 +1,12 @@
1
1
  var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
2
2
  'use strict';
3
3
 
4
- /**
4
+ /**s
5
5
  * Alert component - mimics MUI Alert appearance and behavior
6
6
  */
7
7
  function Alert(props) {
8
- const { children, severity = 'info', variant = 'standard', icon, onClose, sx } = props;
9
- const classes = ['mui-alert', `mui-alert-${severity}`, `mui-alert-${variant}`, props.class ? props.class : '']
10
- .filter(Boolean)
11
- .join(' ');
8
+ const { children, severity = 'info', variant = 'standard', icon, 'kt:close': onClose, sx } = props;
9
+ const classes = `mui-alert mui-alert-${severity} mui-alert-${variant} ${props.class ? props.class : ''}`;
12
10
  // Convert sx object to style string
13
11
  let styleString = props.style || '';
14
12
  if (sx) {
@@ -46,12 +44,12 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
46
44
  return alert;
47
45
  }
48
46
 
49
- const emptyFn$4 = () => { };
47
+ const emptyFn$3 = () => { };
50
48
  /**
51
49
  * Button component - mimics MUI Button appearance and behavior
52
50
  */
53
51
  function Button(props) {
54
- const { children, variant = 'text', color = 'primary', size = 'medium', disabled = false, fullWidth = false, iconOnly = false, startIcon, endIcon, type = 'button', 'on:click': onClick = emptyFn$4, } = props;
52
+ const { children, variant = 'text', color = 'primary', size = 'medium', disabled = false, fullWidth = false, iconOnly = false, startIcon, endIcon, type = 'button', 'on:click': onClick = emptyFn$3, } = props;
55
53
  const classes = [
56
54
  'mui-button',
57
55
  `mui-button-${variant}`,
@@ -93,7 +91,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
93
91
  return (jsxRuntime.jsxs("button", { class: classes, style: props.style ? props.style : '', type: type, disabled: disabled, "on:click": handleClick, children: [startIcon && jsxRuntime.jsx("span", { class: "mui-button-start-icon", children: startIcon }), jsxRuntime.jsx("span", { class: "mui-button-label", children: children }), endIcon && jsxRuntime.jsx("span", { class: "mui-button-end-icon", children: endIcon }), jsxRuntime.jsx("span", { class: "mui-button-ripple" })] }));
94
92
  }
95
93
 
96
- const emptyFn$3 = () => { };
94
+ const emptyFn$2 = () => { };
97
95
  /**
98
96
  * Checkbox component - mimics MUI Checkbox appearance and behavior
99
97
  */
@@ -120,7 +118,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
120
118
  toggleIcon(checked, indeterminate);
121
119
  onChange(checked, value);
122
120
  };
123
- let { checked = false, value = '', label = '', size = 'medium', 'mui:change': onChange = emptyFn$3, disabled = false, color = 'primary', indeterminate = false, } = props;
121
+ let { checked = false, value = '', label = '', size = 'medium', 'kt:change': onChange = emptyFn$2, disabled = false, color = 'primary', indeterminate = false, } = props;
124
122
  const inputRef = kt_js.ref();
125
123
  // Unchecked icon
126
124
  const uncheckedIcon = (jsxRuntime.jsx("span", { class: "mui-checkbox-icon-unchecked", children: jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", children: jsxRuntime.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" }) }) }));
@@ -149,7 +147,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
149
147
  * CheckboxGroup component - groups multiple checkboxes together
150
148
  */
151
149
  function CheckboxGroup(props) {
152
- let { value = [], size = 'medium', 'mui:change': onChange = emptyFn$3, row = false } = props;
150
+ let { value = [], size = 'medium', 'kt:change': onChange = emptyFn$2, row = false } = props;
153
151
  let selectedValues = new Set(value);
154
152
  const changeHandler = (checked, checkboxValue) => {
155
153
  if (checked) {
@@ -163,15 +161,15 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
163
161
  const checkboxes = props.options.map((o) => {
164
162
  o.size = size;
165
163
  o.checked = selectedValues.has(o.value);
166
- const originalChange = o['mui:change'];
164
+ const originalChange = o['kt:change'];
167
165
  if (originalChange) {
168
- o['mui:change'] = (checked, value) => {
166
+ o['kt:change'] = (checked, value) => {
169
167
  originalChange(checked, value);
170
168
  changeHandler(checked, value);
171
169
  };
172
170
  }
173
171
  else {
174
- o['mui:change'] = changeHandler;
172
+ o['kt:change'] = changeHandler;
175
173
  }
176
174
  return Checkbox(o);
177
175
  });
@@ -191,13 +189,13 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
191
189
  return container;
192
190
  }
193
191
 
194
- const noop = () => { };
192
+ const noop$1 = () => { };
195
193
  /**
196
194
  * Dialog component - mimics MUI Dialog appearance and behavior
197
195
  * Only handles open/close state, title and content are passed as props
198
196
  */
199
197
  function Dialog(props) {
200
- let { open = false, 'mui:close': onClose = noop, title, children, actions, maxWidth = 'sm', fullWidth = false, } = props;
198
+ let { open = false, 'kt:close': onClose = noop$1, title, children, actions, maxWidth = 'sm', fullWidth = false, } = props;
201
199
  // Handle ESC key - store handler for cleanup
202
200
  const keyDownHandler = (e) => {
203
201
  if (e.key === 'Escape') {
@@ -282,8 +280,42 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
282
280
  * - can alse be used to store normal values, but it is not reactive.
283
281
  * @param value mostly an HTMLElement
284
282
  */
285
- function ref(value) {
286
- return { value: value, isKT: true };
283
+ function ref(value, onChange) {
284
+ let _value = value;
285
+ let _onChanges = [];
286
+ return {
287
+ isKT: true,
288
+ get value() {
289
+ return _value;
290
+ },
291
+ set value(newValue) {
292
+ if (newValue === _value) {
293
+ return;
294
+ }
295
+ // replace the old node with the new one in the DOM if both are nodes
296
+ if (_value instanceof Node && newValue instanceof Node) {
297
+ if (newValue.contains(_value)) {
298
+ _value.remove();
299
+ }
300
+ _value.replaceWith(newValue);
301
+ }
302
+ const oldValue = _value;
303
+ _value = newValue;
304
+ for (let i = 0; i < _onChanges.length; i++) {
305
+ _onChanges[i](newValue, oldValue);
306
+ }
307
+ },
308
+ addOnChange: (callback) => _onChanges.push(callback),
309
+ removeOnChange: (callback) => {
310
+ for (let i = _onChanges.length - 1; i >= 0; i--) {
311
+ if (_onChanges[i] === callback) {
312
+ _onChanges.splice(i, 1);
313
+ return true;
314
+ }
315
+ }
316
+ return false;
317
+ },
318
+ };
287
319
  }
288
320
 
289
321
  /**
@@ -327,64 +359,134 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
327
359
  return container;
328
360
  }
329
361
 
330
- const emptyFn$2 = () => { };
362
+ const noop = () => { };
331
363
  /**
332
364
  * TextField component - mimics MUI TextField appearance and behavior
333
365
  */
334
366
  function TextField(props) {
335
- const { label = '', placeholder = '', value = '', type = 'text', disabled = false, readonly = false, required = false, error = false, helperText = '', fullWidth = false, multiline = false, rows = 3, maxRows = 10, size = 'medium', 'mui:input': onInput = emptyFn$2, 'mui:change': onChange = emptyFn$2, 'mui:blur': onBlur = emptyFn$2, 'mui:focus': onFocus = emptyFn$2, } = props;
367
+ let { label = '', placeholder = '', value = '', type = 'text', disabled = false, readonly = false, required = false, error = false, helperText = '', fullWidth = false, multiline = false, rows = 3, size = 'medium', 'kt:input': onInput = noop, 'kt-trim:input': onInputTrim = noop, 'kt:change': onChange = noop, 'kt-trim:change': onChangeTrim = noop, 'kt:blur': onBlur = noop, 'kt:focus': onFocus = noop, } = props;
336
368
  let isFocused = false;
337
- const inputRef = kt_js.ref();
369
+ const helperTextEl = jsxRuntime.jsx("p", { class: "mui-textfield-helper-text", children: helperText });
338
370
  // Update container classes
339
- const updateClasses = () => {
340
- const hasValue = inputRef.value?.value || '';
341
- const classes = [
371
+ const updateContainerClass = () => {
372
+ container.className = [
342
373
  'mui-textfield-root',
343
374
  `mui-textfield-size-${size}`,
344
375
  isFocused ? 'mui-textfield-focused' : '',
345
376
  error ? 'mui-textfield-error' : '',
346
377
  disabled ? 'mui-textfield-disabled' : '',
347
378
  fullWidth ? 'mui-textfield-fullwidth' : '',
348
- label && (isFocused || hasValue) ? 'mui-textfield-has-value' : '',
379
+ label && isFocused && inputEl.value ? 'mui-textfield-has-value' : '',
349
380
  label ? '' : 'mui-textfield-no-label',
350
- ];
351
- container.className = classes.join(' ');
381
+ ].join(' ');
352
382
  };
353
383
  const handleInput = (e) => {
354
384
  const target = e.target;
355
- updateClasses();
385
+ updateContainerClass();
356
386
  onInput(target.value, e);
387
+ onInputTrim(target.value.trim(), e);
357
388
  };
358
389
  const handleChange = (e) => {
359
390
  const target = e.target;
360
391
  onChange(target.value, e);
392
+ onChangeTrim(target.value.trim(), e);
361
393
  };
362
394
  const handleFocus = (e) => {
363
395
  isFocused = true;
364
- updateClasses();
396
+ updateContainerClass();
365
397
  const target = e.target;
366
398
  onFocus(target.value, e);
367
399
  };
368
400
  const handleBlur = (e) => {
369
401
  isFocused = false;
370
- updateClasses();
402
+ updateContainerClass();
371
403
  const target = e.target;
372
404
  onBlur(target.value, e);
373
405
  };
374
406
  // Create input or textarea element
375
407
  // Only show placeholder when label is floating (focused or has value)
376
408
  const getPlaceholder = () => (label && !isFocused && !value ? '' : placeholder);
377
- const inputElement = multiline ? (jsxRuntime.jsx("textarea", { ref: inputRef, 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 })) : (jsxRuntime.jsx("input", { ref: inputRef, 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 }));
378
- const container = (jsxRuntime.jsxs("div", { class: 'mui-textfield-root ' + (props.class ? props.class : ''), style: props.style ? props.style : '', children: [jsxRuntime.jsxs("div", { class: "mui-textfield-wrapper", children: [jsxRuntime.jsxs("label", { "k-if": label, class: "mui-textfield-label", children: [label, required && jsxRuntime.jsx("span", { class: "mui-textfield-required", children: "*" })] }), jsxRuntime.jsx("div", { class: "mui-textfield-input-wrapper", children: inputElement }), jsxRuntime.jsx("fieldset", { class: "mui-textfield-fieldset", children: jsxRuntime.jsx("legend", { "k-if": label, class: "mui-textfield-legend", children: jsxRuntime.jsxs("span", { children: [label, required && '*'] }) }) })] }), helperText && jsxRuntime.jsx("p", { class: "mui-textfield-helper-text", children: helperText })] }));
409
+ const inputEl = multiline
410
+ ? (jsxRuntime.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 }))
411
+ : (jsxRuntime.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 }));
412
+ const wrapperRef = kt_js.createRedrawable(() => (jsxRuntime.jsxs("div", { class: "mui-textfield-wrapper", children: [jsxRuntime.jsxs("label", { "k-if": label, class: "mui-textfield-label", children: [label, required && jsxRuntime.jsx("span", { class: "mui-textfield-required", children: "*" })] }), jsxRuntime.jsx("div", { class: "mui-textfield-input-wrapper", children: inputEl }), jsxRuntime.jsx("fieldset", { class: "mui-textfield-fieldset", children: jsxRuntime.jsx("legend", { "k-if": label, class: "mui-textfield-legend", children: jsxRuntime.jsxs("span", { children: [label, required && '*'] }) }) })] })));
413
+ const container = (jsxRuntime.jsxs("div", { class: 'mui-textfield-root ' + (props.class ? props.class : ''), style: props.style ? props.style : '', children: [wrapperRef, helperTextEl] }));
379
414
  // Initialize classes
380
- setTimeout(() => updateClasses(), 0);
381
- Object.defineProperty(container, 'value', {
382
- get() {
383
- return inputRef.value.value;
415
+ setTimeout(() => updateContainerClass(), 0);
416
+ Object.defineProperties(container, {
417
+ value: {
418
+ get() {
419
+ return inputEl.value;
420
+ },
421
+ set(newValue) {
422
+ inputEl.value = newValue;
423
+ updateContainerClass();
424
+ },
384
425
  },
385
- set(newValue) {
386
- inputRef.value.value = newValue;
387
- updateClasses();
426
+ label: {
427
+ get() {
428
+ return label;
429
+ },
430
+ set(newLabel) {
431
+ label = newLabel;
432
+ wrapperRef.value.redraw(); // label takes too much and should be redrawn
433
+ updateContainerClass();
434
+ },
435
+ },
436
+ placeholder: {
437
+ get() {
438
+ return placeholder;
439
+ },
440
+ set(newPlaceholder) {
441
+ placeholder = newPlaceholder;
442
+ inputEl.placeholder = getPlaceholder();
443
+ },
444
+ },
445
+ type: {
446
+ get() {
447
+ return type;
448
+ },
449
+ set(newType) {
450
+ type = newType || 'text';
451
+ inputEl.type = type;
452
+ },
453
+ },
454
+ disabled: {
455
+ get() {
456
+ return disabled;
457
+ },
458
+ set(val) {
459
+ disabled = !!val;
460
+ inputEl.disabled = disabled;
461
+ container.classList.toggle('mui-textfield-disabled', disabled);
462
+ },
463
+ },
464
+ readonly: {
465
+ get() {
466
+ return readonly;
467
+ },
468
+ set(val) {
469
+ readonly = Boolean(val);
470
+ inputEl.readOnly = readonly;
471
+ },
472
+ },
473
+ error: {
474
+ get() {
475
+ return error;
476
+ },
477
+ set(val) {
478
+ error = Boolean(val);
479
+ container.classList.toggle('mui-textfield-error', error);
480
+ },
481
+ },
482
+ helperText: {
483
+ get() {
484
+ return helperText;
485
+ },
486
+ set(text) {
487
+ helperTextEl.textContent = text;
488
+ helperTextEl.style.display = text ? 'block' : 'none';
489
+ },
388
490
  },
389
491
  });
390
492
  return container;
@@ -408,7 +510,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
408
510
  toggleIcon(checked);
409
511
  onChange(checked, value);
410
512
  };
411
- const { checked: initChecked = false, value = '', text = '', size = 'small', 'mui:change': onChange = emptyFn$1, disabled: initDisabled = false, color = 'primary', } = props;
513
+ const { checked: initChecked = false, value = '', text = '', size = 'small', 'kt:change': onChange = emptyFn$1, disabled: initDisabled = false, color = 'primary', } = props;
412
514
  const inputRef = kt_js.ref();
413
515
  let checked = initChecked;
414
516
  let disabled = initDisabled;
@@ -428,7 +530,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
428
530
  * RadioGroup component - groups multiple radios together
429
531
  */
430
532
  function RadioGroup(props) {
431
- const { value = '', size = 'small', 'mui:change': onChange = emptyFn$1, row = false } = props;
533
+ const { value = '', size = 'small', 'kt:change': onChange = emptyFn$1, row = false } = props;
432
534
  const changeHandler = (checked, value) => {
433
535
  if (checked) {
434
536
  onChange(value);
@@ -438,15 +540,15 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
438
540
  const radios = props.options.map((o) => {
439
541
  o.size = size;
440
542
  o.checked = value === o.value;
441
- const originalChange = o['mui:change'];
543
+ const originalChange = o['kt:change'];
442
544
  if (originalChange) {
443
- o['mui:change'] = (checked, newValue) => {
545
+ o['kt:change'] = (checked, newValue) => {
444
546
  originalChange(checked, newValue);
445
547
  changeHandler(checked, newValue);
446
548
  };
447
549
  }
448
550
  else {
449
- o['mui:change'] = changeHandler;
551
+ o['kt:change'] = changeHandler;
450
552
  }
451
553
  return Radio(o);
452
554
  });
@@ -458,7 +560,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
458
560
  * Select component - mimics MUI Select appearance and behavior
459
561
  */
460
562
  function Select(props) {
461
- let { value = '', options = [], label = '', placeholder = '', size = 'medium', 'mui:change': onChange = emptyFn, fullWidth = false, disabled = false, } = props;
563
+ let { value = '', options = [], label = '', placeholder = '', size = 'medium', 'kt:change': onChange = emptyFn, fullWidth = false, disabled = false, } = props;
462
564
  let isOpen = false;
463
565
  let isFocused = false;
464
566
  const selectRef = kt_js.ref();
package/dist/index.mjs CHANGED
@@ -1,14 +1,12 @@
1
1
  import { jsxs, jsx } from 'kt.js/jsx-runtime';
2
2
  import { ref as ref$1, createRedrawable } from 'kt.js';
3
3
 
4
- /**
4
+ /**s
5
5
  * Alert component - mimics MUI Alert appearance and behavior
6
6
  */
7
7
  function Alert(props) {
8
- const { children, severity = 'info', variant = 'standard', icon, onClose, sx } = props;
9
- const classes = ['mui-alert', `mui-alert-${severity}`, `mui-alert-${variant}`, props.class ? props.class : '']
10
- .filter(Boolean)
11
- .join(' ');
8
+ const { children, severity = 'info', variant = 'standard', icon, 'kt:close': onClose, sx } = props;
9
+ const classes = `mui-alert mui-alert-${severity} mui-alert-${variant} ${props.class ? props.class : ''}`;
12
10
  // Convert sx object to style string
13
11
  let styleString = props.style || '';
14
12
  if (sx) {
@@ -46,12 +44,12 @@ function Alert(props) {
46
44
  return alert;
47
45
  }
48
46
 
49
- const emptyFn$4 = () => { };
47
+ const emptyFn$3 = () => { };
50
48
  /**
51
49
  * Button component - mimics MUI Button appearance and behavior
52
50
  */
53
51
  function Button(props) {
54
- const { children, variant = 'text', color = 'primary', size = 'medium', disabled = false, fullWidth = false, iconOnly = false, startIcon, endIcon, type = 'button', 'on:click': onClick = emptyFn$4, } = props;
52
+ const { children, variant = 'text', color = 'primary', size = 'medium', disabled = false, fullWidth = false, iconOnly = false, startIcon, endIcon, type = 'button', 'on:click': onClick = emptyFn$3, } = props;
55
53
  const classes = [
56
54
  'mui-button',
57
55
  `mui-button-${variant}`,
@@ -93,7 +91,7 @@ function Button(props) {
93
91
  return (jsxs("button", { class: classes, style: props.style ? 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 }), jsx("span", { class: "mui-button-ripple" })] }));
94
92
  }
95
93
 
96
- const emptyFn$3 = () => { };
94
+ const emptyFn$2 = () => { };
97
95
  /**
98
96
  * Checkbox component - mimics MUI Checkbox appearance and behavior
99
97
  */
@@ -120,7 +118,7 @@ function Checkbox(props) {
120
118
  toggleIcon(checked, indeterminate);
121
119
  onChange(checked, value);
122
120
  };
123
- let { checked = false, value = '', label = '', size = 'medium', 'mui:change': onChange = emptyFn$3, disabled = false, color = 'primary', indeterminate = false, } = props;
121
+ let { checked = false, value = '', label = '', size = 'medium', 'kt:change': onChange = emptyFn$2, disabled = false, color = 'primary', indeterminate = false, } = props;
124
122
  const inputRef = ref$1();
125
123
  // Unchecked icon
126
124
  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" }) }) }));
@@ -149,7 +147,7 @@ function Checkbox(props) {
149
147
  * CheckboxGroup component - groups multiple checkboxes together
150
148
  */
151
149
  function CheckboxGroup(props) {
152
- let { value = [], size = 'medium', 'mui:change': onChange = emptyFn$3, row = false } = props;
150
+ let { value = [], size = 'medium', 'kt:change': onChange = emptyFn$2, row = false } = props;
153
151
  let selectedValues = new Set(value);
154
152
  const changeHandler = (checked, checkboxValue) => {
155
153
  if (checked) {
@@ -163,15 +161,15 @@ function CheckboxGroup(props) {
163
161
  const checkboxes = props.options.map((o) => {
164
162
  o.size = size;
165
163
  o.checked = selectedValues.has(o.value);
166
- const originalChange = o['mui:change'];
164
+ const originalChange = o['kt:change'];
167
165
  if (originalChange) {
168
- o['mui:change'] = (checked, value) => {
166
+ o['kt:change'] = (checked, value) => {
169
167
  originalChange(checked, value);
170
168
  changeHandler(checked, value);
171
169
  };
172
170
  }
173
171
  else {
174
- o['mui:change'] = changeHandler;
172
+ o['kt:change'] = changeHandler;
175
173
  }
176
174
  return Checkbox(o);
177
175
  });
@@ -191,13 +189,13 @@ function CheckboxGroup(props) {
191
189
  return container;
192
190
  }
193
191
 
194
- const noop = () => { };
192
+ const noop$1 = () => { };
195
193
  /**
196
194
  * Dialog component - mimics MUI Dialog appearance and behavior
197
195
  * Only handles open/close state, title and content are passed as props
198
196
  */
199
197
  function Dialog(props) {
200
- let { open = false, 'mui:close': onClose = noop, title, children, actions, maxWidth = 'sm', fullWidth = false, } = props;
198
+ let { open = false, 'kt:close': onClose = noop$1, title, children, actions, maxWidth = 'sm', fullWidth = false, } = props;
201
199
  // Handle ESC key - store handler for cleanup
202
200
  const keyDownHandler = (e) => {
203
201
  if (e.key === 'Escape') {
@@ -282,8 +280,42 @@ document.createElement('div');
282
280
  * - can alse be used to store normal values, but it is not reactive.
283
281
  * @param value mostly an HTMLElement
284
282
  */
285
- function ref(value) {
286
- return { value: value, isKT: true };
283
+ function ref(value, onChange) {
284
+ let _value = value;
285
+ let _onChanges = [];
286
+ return {
287
+ isKT: true,
288
+ get value() {
289
+ return _value;
290
+ },
291
+ set value(newValue) {
292
+ if (newValue === _value) {
293
+ return;
294
+ }
295
+ // replace the old node with the new one in the DOM if both are nodes
296
+ if (_value instanceof Node && newValue instanceof Node) {
297
+ if (newValue.contains(_value)) {
298
+ _value.remove();
299
+ }
300
+ _value.replaceWith(newValue);
301
+ }
302
+ const oldValue = _value;
303
+ _value = newValue;
304
+ for (let i = 0; i < _onChanges.length; i++) {
305
+ _onChanges[i](newValue, oldValue);
306
+ }
307
+ },
308
+ addOnChange: (callback) => _onChanges.push(callback),
309
+ removeOnChange: (callback) => {
310
+ for (let i = _onChanges.length - 1; i >= 0; i--) {
311
+ if (_onChanges[i] === callback) {
312
+ _onChanges.splice(i, 1);
313
+ return true;
314
+ }
315
+ }
316
+ return false;
317
+ },
318
+ };
287
319
  }
288
320
 
289
321
  /**
@@ -327,64 +359,134 @@ function LinearProgress(props) {
327
359
  return container;
328
360
  }
329
361
 
330
- const emptyFn$2 = () => { };
362
+ const noop = () => { };
331
363
  /**
332
364
  * TextField component - mimics MUI TextField appearance and behavior
333
365
  */
334
366
  function TextField(props) {
335
- const { label = '', placeholder = '', value = '', type = 'text', disabled = false, readonly = false, required = false, error = false, helperText = '', fullWidth = false, multiline = false, rows = 3, maxRows = 10, size = 'medium', 'mui:input': onInput = emptyFn$2, 'mui:change': onChange = emptyFn$2, 'mui:blur': onBlur = emptyFn$2, 'mui:focus': onFocus = emptyFn$2, } = props;
367
+ let { label = '', placeholder = '', value = '', type = 'text', disabled = false, readonly = false, required = false, error = false, helperText = '', fullWidth = false, multiline = false, rows = 3, size = 'medium', 'kt:input': onInput = noop, 'kt-trim:input': onInputTrim = noop, 'kt:change': onChange = noop, 'kt-trim:change': onChangeTrim = noop, 'kt:blur': onBlur = noop, 'kt:focus': onFocus = noop, } = props;
336
368
  let isFocused = false;
337
- const inputRef = ref$1();
369
+ const helperTextEl = jsx("p", { class: "mui-textfield-helper-text", children: helperText });
338
370
  // Update container classes
339
- const updateClasses = () => {
340
- const hasValue = inputRef.value?.value || '';
341
- const classes = [
371
+ const updateContainerClass = () => {
372
+ container.className = [
342
373
  'mui-textfield-root',
343
374
  `mui-textfield-size-${size}`,
344
375
  isFocused ? 'mui-textfield-focused' : '',
345
376
  error ? 'mui-textfield-error' : '',
346
377
  disabled ? 'mui-textfield-disabled' : '',
347
378
  fullWidth ? 'mui-textfield-fullwidth' : '',
348
- label && (isFocused || hasValue) ? 'mui-textfield-has-value' : '',
379
+ label && isFocused && inputEl.value ? 'mui-textfield-has-value' : '',
349
380
  label ? '' : 'mui-textfield-no-label',
350
- ];
351
- container.className = classes.join(' ');
381
+ ].join(' ');
352
382
  };
353
383
  const handleInput = (e) => {
354
384
  const target = e.target;
355
- updateClasses();
385
+ updateContainerClass();
356
386
  onInput(target.value, e);
387
+ onInputTrim(target.value.trim(), e);
357
388
  };
358
389
  const handleChange = (e) => {
359
390
  const target = e.target;
360
391
  onChange(target.value, e);
392
+ onChangeTrim(target.value.trim(), e);
361
393
  };
362
394
  const handleFocus = (e) => {
363
395
  isFocused = true;
364
- updateClasses();
396
+ updateContainerClass();
365
397
  const target = e.target;
366
398
  onFocus(target.value, e);
367
399
  };
368
400
  const handleBlur = (e) => {
369
401
  isFocused = false;
370
- updateClasses();
402
+ updateContainerClass();
371
403
  const target = e.target;
372
404
  onBlur(target.value, e);
373
405
  };
374
406
  // Create input or textarea element
375
407
  // Only show placeholder when label is floating (focused or has value)
376
408
  const getPlaceholder = () => (label && !isFocused && !value ? '' : placeholder);
377
- const inputElement = multiline ? (jsx("textarea", { ref: inputRef, 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 })) : (jsx("input", { ref: inputRef, 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 }));
378
- const container = (jsxs("div", { class: 'mui-textfield-root ' + (props.class ? props.class : ''), style: props.style ? props.style : '', children: [jsxs("div", { class: "mui-textfield-wrapper", 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: inputElement }), jsx("fieldset", { class: "mui-textfield-fieldset", children: jsx("legend", { "k-if": label, class: "mui-textfield-legend", children: jsxs("span", { children: [label, required && '*'] }) }) })] }), helperText && jsx("p", { class: "mui-textfield-helper-text", children: helperText })] }));
409
+ const inputEl = multiline
410
+ ? (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 }))
411
+ : (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 }));
412
+ const wrapperRef = createRedrawable(() => (jsxs("div", { class: "mui-textfield-wrapper", 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 && '*'] }) }) })] })));
413
+ const container = (jsxs("div", { class: 'mui-textfield-root ' + (props.class ? props.class : ''), style: props.style ? props.style : '', children: [wrapperRef, helperTextEl] }));
379
414
  // Initialize classes
380
- setTimeout(() => updateClasses(), 0);
381
- Object.defineProperty(container, 'value', {
382
- get() {
383
- return inputRef.value.value;
415
+ setTimeout(() => updateContainerClass(), 0);
416
+ Object.defineProperties(container, {
417
+ value: {
418
+ get() {
419
+ return inputEl.value;
420
+ },
421
+ set(newValue) {
422
+ inputEl.value = newValue;
423
+ updateContainerClass();
424
+ },
384
425
  },
385
- set(newValue) {
386
- inputRef.value.value = newValue;
387
- updateClasses();
426
+ label: {
427
+ get() {
428
+ return label;
429
+ },
430
+ set(newLabel) {
431
+ label = newLabel;
432
+ wrapperRef.value.redraw(); // label takes too much and should be redrawn
433
+ updateContainerClass();
434
+ },
435
+ },
436
+ placeholder: {
437
+ get() {
438
+ return placeholder;
439
+ },
440
+ set(newPlaceholder) {
441
+ placeholder = newPlaceholder;
442
+ inputEl.placeholder = getPlaceholder();
443
+ },
444
+ },
445
+ type: {
446
+ get() {
447
+ return type;
448
+ },
449
+ set(newType) {
450
+ type = newType || 'text';
451
+ inputEl.type = type;
452
+ },
453
+ },
454
+ disabled: {
455
+ get() {
456
+ return disabled;
457
+ },
458
+ set(val) {
459
+ disabled = !!val;
460
+ inputEl.disabled = disabled;
461
+ container.classList.toggle('mui-textfield-disabled', disabled);
462
+ },
463
+ },
464
+ readonly: {
465
+ get() {
466
+ return readonly;
467
+ },
468
+ set(val) {
469
+ readonly = Boolean(val);
470
+ inputEl.readOnly = readonly;
471
+ },
472
+ },
473
+ error: {
474
+ get() {
475
+ return error;
476
+ },
477
+ set(val) {
478
+ error = Boolean(val);
479
+ container.classList.toggle('mui-textfield-error', error);
480
+ },
481
+ },
482
+ helperText: {
483
+ get() {
484
+ return helperText;
485
+ },
486
+ set(text) {
487
+ helperTextEl.textContent = text;
488
+ helperTextEl.style.display = text ? 'block' : 'none';
489
+ },
388
490
  },
389
491
  });
390
492
  return container;
@@ -408,7 +510,7 @@ function Radio(props) {
408
510
  toggleIcon(checked);
409
511
  onChange(checked, value);
410
512
  };
411
- const { checked: initChecked = false, value = '', text = '', size = 'small', 'mui:change': onChange = emptyFn$1, disabled: initDisabled = false, color = 'primary', } = props;
513
+ const { checked: initChecked = false, value = '', text = '', size = 'small', 'kt:change': onChange = emptyFn$1, disabled: initDisabled = false, color = 'primary', } = props;
412
514
  const inputRef = ref$1();
413
515
  let checked = initChecked;
414
516
  let disabled = initDisabled;
@@ -428,7 +530,7 @@ function Radio(props) {
428
530
  * RadioGroup component - groups multiple radios together
429
531
  */
430
532
  function RadioGroup(props) {
431
- const { value = '', size = 'small', 'mui:change': onChange = emptyFn$1, row = false } = props;
533
+ const { value = '', size = 'small', 'kt:change': onChange = emptyFn$1, row = false } = props;
432
534
  const changeHandler = (checked, value) => {
433
535
  if (checked) {
434
536
  onChange(value);
@@ -438,15 +540,15 @@ function RadioGroup(props) {
438
540
  const radios = props.options.map((o) => {
439
541
  o.size = size;
440
542
  o.checked = value === o.value;
441
- const originalChange = o['mui:change'];
543
+ const originalChange = o['kt:change'];
442
544
  if (originalChange) {
443
- o['mui:change'] = (checked, newValue) => {
545
+ o['kt:change'] = (checked, newValue) => {
444
546
  originalChange(checked, newValue);
445
547
  changeHandler(checked, newValue);
446
548
  };
447
549
  }
448
550
  else {
449
- o['mui:change'] = changeHandler;
551
+ o['kt:change'] = changeHandler;
450
552
  }
451
553
  return Radio(o);
452
554
  });
@@ -458,7 +560,7 @@ const emptyFn = () => { };
458
560
  * Select component - mimics MUI Select appearance and behavior
459
561
  */
460
562
  function Select(props) {
461
- let { value = '', options = [], label = '', placeholder = '', size = 'medium', 'mui:change': onChange = emptyFn, fullWidth = false, disabled = false, } = props;
563
+ let { value = '', options = [], label = '', placeholder = '', size = 'medium', 'kt:change': onChange = emptyFn, fullWidth = false, disabled = false, } = props;
462
564
  let isOpen = false;
463
565
  let isFocused = false;
464
566
  const selectRef = ref$1();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/mui",
3
- "version": "0.17.8",
3
+ "version": "0.18.0",
4
4
  "description": "Material-UI inspired components for kt.js - pre-styled UI components",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",
@@ -35,7 +35,7 @@
35
35
  "directory": "packages/mui"
36
36
  },
37
37
  "dependencies": {
38
- "@ktjs/core": "0.17.4"
38
+ "@ktjs/core": "0.18.4"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "rollup -c rollup.config.mjs",