@ktjs/mui 0.17.9 → 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,7 +9,7 @@ interface AlertProps {
9
9
  severity?: 'error' | 'warning' | 'info' | 'success';
10
10
  variant?: 'standard' | 'filled' | 'outlined';
11
11
  icon?: HTMLElement | KTHTMLElement | false;
12
- 'mui:close'?: () => void;
12
+ 'kt:close'?: () => void;
13
13
  }
14
14
  /**s
15
15
  * Alert component - mimics MUI Alert appearance and behavior
@@ -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
  }
@@ -5,7 +5,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
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, 'mui:close': onClose, sx } = props;
8
+ const { children, severity = 'info', variant = 'standard', icon, 'kt:close': onClose, sx } = props;
9
9
  const classes = `mui-alert mui-alert-${severity} mui-alert-${variant} ${props.class ? props.class : ''}`;
10
10
  // Convert sx object to style string
11
11
  let styleString = props.style || '';
@@ -44,12 +44,12 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
44
44
  return alert;
45
45
  }
46
46
 
47
- const emptyFn$4 = () => { };
47
+ const emptyFn$3 = () => { };
48
48
  /**
49
49
  * Button component - mimics MUI Button appearance and behavior
50
50
  */
51
51
  function Button(props) {
52
- 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;
53
53
  const classes = [
54
54
  'mui-button',
55
55
  `mui-button-${variant}`,
@@ -91,7 +91,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
91
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" })] }));
92
92
  }
93
93
 
94
- const emptyFn$3 = () => { };
94
+ const emptyFn$2 = () => { };
95
95
  /**
96
96
  * Checkbox component - mimics MUI Checkbox appearance and behavior
97
97
  */
@@ -118,7 +118,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
118
118
  toggleIcon(checked, indeterminate);
119
119
  onChange(checked, value);
120
120
  };
121
- 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;
122
122
  const inputRef = kt_js.ref();
123
123
  // Unchecked icon
124
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" }) }) }));
@@ -147,7 +147,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
147
147
  * CheckboxGroup component - groups multiple checkboxes together
148
148
  */
149
149
  function CheckboxGroup(props) {
150
- 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;
151
151
  let selectedValues = new Set(value);
152
152
  const changeHandler = (checked, checkboxValue) => {
153
153
  if (checked) {
@@ -161,15 +161,15 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
161
161
  const checkboxes = props.options.map((o) => {
162
162
  o.size = size;
163
163
  o.checked = selectedValues.has(o.value);
164
- const originalChange = o['mui:change'];
164
+ const originalChange = o['kt:change'];
165
165
  if (originalChange) {
166
- o['mui:change'] = (checked, value) => {
166
+ o['kt:change'] = (checked, value) => {
167
167
  originalChange(checked, value);
168
168
  changeHandler(checked, value);
169
169
  };
170
170
  }
171
171
  else {
172
- o['mui:change'] = changeHandler;
172
+ o['kt:change'] = changeHandler;
173
173
  }
174
174
  return Checkbox(o);
175
175
  });
@@ -189,13 +189,13 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
189
189
  return container;
190
190
  }
191
191
 
192
- const noop = () => { };
192
+ const noop$1 = () => { };
193
193
  /**
194
194
  * Dialog component - mimics MUI Dialog appearance and behavior
195
195
  * Only handles open/close state, title and content are passed as props
196
196
  */
197
197
  function Dialog(props) {
198
- 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;
199
199
  // Handle ESC key - store handler for cleanup
200
200
  const keyDownHandler = (e) => {
201
201
  if (e.key === 'Escape') {
@@ -280,8 +280,42 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
280
280
  * - can alse be used to store normal values, but it is not reactive.
281
281
  * @param value mostly an HTMLElement
282
282
  */
283
- function ref(value) {
284
- 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
+ };
285
319
  }
286
320
 
287
321
  /**
@@ -325,64 +359,134 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
325
359
  return container;
326
360
  }
327
361
 
328
- const emptyFn$2 = () => { };
362
+ const noop = () => { };
329
363
  /**
330
364
  * TextField component - mimics MUI TextField appearance and behavior
331
365
  */
332
366
  function TextField(props) {
333
- 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;
334
368
  let isFocused = false;
335
- const inputRef = kt_js.ref();
369
+ const helperTextEl = jsxRuntime.jsx("p", { class: "mui-textfield-helper-text", children: helperText });
336
370
  // Update container classes
337
- const updateClasses = () => {
338
- const hasValue = inputRef.value?.value || '';
339
- const classes = [
371
+ const updateContainerClass = () => {
372
+ container.className = [
340
373
  'mui-textfield-root',
341
374
  `mui-textfield-size-${size}`,
342
375
  isFocused ? 'mui-textfield-focused' : '',
343
376
  error ? 'mui-textfield-error' : '',
344
377
  disabled ? 'mui-textfield-disabled' : '',
345
378
  fullWidth ? 'mui-textfield-fullwidth' : '',
346
- label && (isFocused || hasValue) ? 'mui-textfield-has-value' : '',
379
+ label && isFocused && inputEl.value ? 'mui-textfield-has-value' : '',
347
380
  label ? '' : 'mui-textfield-no-label',
348
- ];
349
- container.className = classes.join(' ');
381
+ ].join(' ');
350
382
  };
351
383
  const handleInput = (e) => {
352
384
  const target = e.target;
353
- updateClasses();
385
+ updateContainerClass();
354
386
  onInput(target.value, e);
387
+ onInputTrim(target.value.trim(), e);
355
388
  };
356
389
  const handleChange = (e) => {
357
390
  const target = e.target;
358
391
  onChange(target.value, e);
392
+ onChangeTrim(target.value.trim(), e);
359
393
  };
360
394
  const handleFocus = (e) => {
361
395
  isFocused = true;
362
- updateClasses();
396
+ updateContainerClass();
363
397
  const target = e.target;
364
398
  onFocus(target.value, e);
365
399
  };
366
400
  const handleBlur = (e) => {
367
401
  isFocused = false;
368
- updateClasses();
402
+ updateContainerClass();
369
403
  const target = e.target;
370
404
  onBlur(target.value, e);
371
405
  };
372
406
  // Create input or textarea element
373
407
  // Only show placeholder when label is floating (focused or has value)
374
408
  const getPlaceholder = () => (label && !isFocused && !value ? '' : placeholder);
375
- 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 }));
376
- 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] }));
377
414
  // Initialize classes
378
- setTimeout(() => updateClasses(), 0);
379
- Object.defineProperty(container, 'value', {
380
- get() {
381
- 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
+ },
382
425
  },
383
- set(newValue) {
384
- inputRef.value.value = newValue;
385
- 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
+ },
386
490
  },
387
491
  });
388
492
  return container;
@@ -406,7 +510,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
406
510
  toggleIcon(checked);
407
511
  onChange(checked, value);
408
512
  };
409
- 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;
410
514
  const inputRef = kt_js.ref();
411
515
  let checked = initChecked;
412
516
  let disabled = initDisabled;
@@ -426,7 +530,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
426
530
  * RadioGroup component - groups multiple radios together
427
531
  */
428
532
  function RadioGroup(props) {
429
- 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;
430
534
  const changeHandler = (checked, value) => {
431
535
  if (checked) {
432
536
  onChange(value);
@@ -436,15 +540,15 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
436
540
  const radios = props.options.map((o) => {
437
541
  o.size = size;
438
542
  o.checked = value === o.value;
439
- const originalChange = o['mui:change'];
543
+ const originalChange = o['kt:change'];
440
544
  if (originalChange) {
441
- o['mui:change'] = (checked, newValue) => {
545
+ o['kt:change'] = (checked, newValue) => {
442
546
  originalChange(checked, newValue);
443
547
  changeHandler(checked, newValue);
444
548
  };
445
549
  }
446
550
  else {
447
- o['mui:change'] = changeHandler;
551
+ o['kt:change'] = changeHandler;
448
552
  }
449
553
  return Radio(o);
450
554
  });
@@ -456,7 +560,7 @@ var __ktjs_mui__ = (function (exports, jsxRuntime, kt_js) {
456
560
  * Select component - mimics MUI Select appearance and behavior
457
561
  */
458
562
  function Select(props) {
459
- 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;
460
564
  let isOpen = false;
461
565
  let isFocused = false;
462
566
  const selectRef = kt_js.ref();
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { ref as ref$1, createRedrawable } from 'kt.js';
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, 'mui:close': onClose, sx } = props;
8
+ const { children, severity = 'info', variant = 'standard', icon, 'kt:close': onClose, sx } = props;
9
9
  const classes = `mui-alert mui-alert-${severity} mui-alert-${variant} ${props.class ? props.class : ''}`;
10
10
  // Convert sx object to style string
11
11
  let styleString = props.style || '';
@@ -44,12 +44,12 @@ function Alert(props) {
44
44
  return alert;
45
45
  }
46
46
 
47
- const emptyFn$4 = () => { };
47
+ const emptyFn$3 = () => { };
48
48
  /**
49
49
  * Button component - mimics MUI Button appearance and behavior
50
50
  */
51
51
  function Button(props) {
52
- 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;
53
53
  const classes = [
54
54
  'mui-button',
55
55
  `mui-button-${variant}`,
@@ -91,7 +91,7 @@ function Button(props) {
91
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" })] }));
92
92
  }
93
93
 
94
- const emptyFn$3 = () => { };
94
+ const emptyFn$2 = () => { };
95
95
  /**
96
96
  * Checkbox component - mimics MUI Checkbox appearance and behavior
97
97
  */
@@ -118,7 +118,7 @@ function Checkbox(props) {
118
118
  toggleIcon(checked, indeterminate);
119
119
  onChange(checked, value);
120
120
  };
121
- 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;
122
122
  const inputRef = ref$1();
123
123
  // Unchecked icon
124
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" }) }) }));
@@ -147,7 +147,7 @@ function Checkbox(props) {
147
147
  * CheckboxGroup component - groups multiple checkboxes together
148
148
  */
149
149
  function CheckboxGroup(props) {
150
- 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;
151
151
  let selectedValues = new Set(value);
152
152
  const changeHandler = (checked, checkboxValue) => {
153
153
  if (checked) {
@@ -161,15 +161,15 @@ function CheckboxGroup(props) {
161
161
  const checkboxes = props.options.map((o) => {
162
162
  o.size = size;
163
163
  o.checked = selectedValues.has(o.value);
164
- const originalChange = o['mui:change'];
164
+ const originalChange = o['kt:change'];
165
165
  if (originalChange) {
166
- o['mui:change'] = (checked, value) => {
166
+ o['kt:change'] = (checked, value) => {
167
167
  originalChange(checked, value);
168
168
  changeHandler(checked, value);
169
169
  };
170
170
  }
171
171
  else {
172
- o['mui:change'] = changeHandler;
172
+ o['kt:change'] = changeHandler;
173
173
  }
174
174
  return Checkbox(o);
175
175
  });
@@ -189,13 +189,13 @@ function CheckboxGroup(props) {
189
189
  return container;
190
190
  }
191
191
 
192
- const noop = () => { };
192
+ const noop$1 = () => { };
193
193
  /**
194
194
  * Dialog component - mimics MUI Dialog appearance and behavior
195
195
  * Only handles open/close state, title and content are passed as props
196
196
  */
197
197
  function Dialog(props) {
198
- 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;
199
199
  // Handle ESC key - store handler for cleanup
200
200
  const keyDownHandler = (e) => {
201
201
  if (e.key === 'Escape') {
@@ -280,8 +280,42 @@ document.createElement('div');
280
280
  * - can alse be used to store normal values, but it is not reactive.
281
281
  * @param value mostly an HTMLElement
282
282
  */
283
- function ref(value) {
284
- 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
+ };
285
319
  }
286
320
 
287
321
  /**
@@ -325,64 +359,134 @@ function LinearProgress(props) {
325
359
  return container;
326
360
  }
327
361
 
328
- const emptyFn$2 = () => { };
362
+ const noop = () => { };
329
363
  /**
330
364
  * TextField component - mimics MUI TextField appearance and behavior
331
365
  */
332
366
  function TextField(props) {
333
- 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;
334
368
  let isFocused = false;
335
- const inputRef = ref$1();
369
+ const helperTextEl = jsx("p", { class: "mui-textfield-helper-text", children: helperText });
336
370
  // Update container classes
337
- const updateClasses = () => {
338
- const hasValue = inputRef.value?.value || '';
339
- const classes = [
371
+ const updateContainerClass = () => {
372
+ container.className = [
340
373
  'mui-textfield-root',
341
374
  `mui-textfield-size-${size}`,
342
375
  isFocused ? 'mui-textfield-focused' : '',
343
376
  error ? 'mui-textfield-error' : '',
344
377
  disabled ? 'mui-textfield-disabled' : '',
345
378
  fullWidth ? 'mui-textfield-fullwidth' : '',
346
- label && (isFocused || hasValue) ? 'mui-textfield-has-value' : '',
379
+ label && isFocused && inputEl.value ? 'mui-textfield-has-value' : '',
347
380
  label ? '' : 'mui-textfield-no-label',
348
- ];
349
- container.className = classes.join(' ');
381
+ ].join(' ');
350
382
  };
351
383
  const handleInput = (e) => {
352
384
  const target = e.target;
353
- updateClasses();
385
+ updateContainerClass();
354
386
  onInput(target.value, e);
387
+ onInputTrim(target.value.trim(), e);
355
388
  };
356
389
  const handleChange = (e) => {
357
390
  const target = e.target;
358
391
  onChange(target.value, e);
392
+ onChangeTrim(target.value.trim(), e);
359
393
  };
360
394
  const handleFocus = (e) => {
361
395
  isFocused = true;
362
- updateClasses();
396
+ updateContainerClass();
363
397
  const target = e.target;
364
398
  onFocus(target.value, e);
365
399
  };
366
400
  const handleBlur = (e) => {
367
401
  isFocused = false;
368
- updateClasses();
402
+ updateContainerClass();
369
403
  const target = e.target;
370
404
  onBlur(target.value, e);
371
405
  };
372
406
  // Create input or textarea element
373
407
  // Only show placeholder when label is floating (focused or has value)
374
408
  const getPlaceholder = () => (label && !isFocused && !value ? '' : placeholder);
375
- 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 }));
376
- 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] }));
377
414
  // Initialize classes
378
- setTimeout(() => updateClasses(), 0);
379
- Object.defineProperty(container, 'value', {
380
- get() {
381
- 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
+ },
382
425
  },
383
- set(newValue) {
384
- inputRef.value.value = newValue;
385
- 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
+ },
386
490
  },
387
491
  });
388
492
  return container;
@@ -406,7 +510,7 @@ function Radio(props) {
406
510
  toggleIcon(checked);
407
511
  onChange(checked, value);
408
512
  };
409
- 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;
410
514
  const inputRef = ref$1();
411
515
  let checked = initChecked;
412
516
  let disabled = initDisabled;
@@ -426,7 +530,7 @@ function Radio(props) {
426
530
  * RadioGroup component - groups multiple radios together
427
531
  */
428
532
  function RadioGroup(props) {
429
- 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;
430
534
  const changeHandler = (checked, value) => {
431
535
  if (checked) {
432
536
  onChange(value);
@@ -436,15 +540,15 @@ function RadioGroup(props) {
436
540
  const radios = props.options.map((o) => {
437
541
  o.size = size;
438
542
  o.checked = value === o.value;
439
- const originalChange = o['mui:change'];
543
+ const originalChange = o['kt:change'];
440
544
  if (originalChange) {
441
- o['mui:change'] = (checked, newValue) => {
545
+ o['kt:change'] = (checked, newValue) => {
442
546
  originalChange(checked, newValue);
443
547
  changeHandler(checked, newValue);
444
548
  };
445
549
  }
446
550
  else {
447
- o['mui:change'] = changeHandler;
551
+ o['kt:change'] = changeHandler;
448
552
  }
449
553
  return Radio(o);
450
554
  });
@@ -456,7 +560,7 @@ const emptyFn = () => { };
456
560
  * Select component - mimics MUI Select appearance and behavior
457
561
  */
458
562
  function Select(props) {
459
- 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;
460
564
  let isOpen = false;
461
565
  let isFocused = false;
462
566
  const selectRef = ref$1();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/mui",
3
- "version": "0.17.9",
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.18.0"
38
+ "@ktjs/core": "0.18.4"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "rollup -c rollup.config.mjs",