tyrell-components 1.0.0-RC6
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/LICENSE +21 -0
- package/README.md +221 -0
- package/css/tyrell.css +1783 -0
- package/dist/tyrell.css +1783 -0
- package/dist/tyrell.js +2 -0
- package/lib/base/ty-component.d.ts +133 -0
- package/lib/base/ty-component.d.ts.map +1 -0
- package/lib/base/ty-component.js +297 -0
- package/lib/base/ty-component.js.map +1 -0
- package/lib/components/button.d.ts +126 -0
- package/lib/components/button.d.ts.map +1 -0
- package/lib/components/button.js +244 -0
- package/lib/components/button.js.map +1 -0
- package/lib/components/calendar-month.d.ts +132 -0
- package/lib/components/calendar-month.d.ts.map +1 -0
- package/lib/components/calendar-month.js +440 -0
- package/lib/components/calendar-month.js.map +1 -0
- package/lib/components/calendar-navigation.d.ts +137 -0
- package/lib/components/calendar-navigation.d.ts.map +1 -0
- package/lib/components/calendar-navigation.js +366 -0
- package/lib/components/calendar-navigation.js.map +1 -0
- package/lib/components/calendar.d.ts +166 -0
- package/lib/components/calendar.d.ts.map +1 -0
- package/lib/components/calendar.js +774 -0
- package/lib/components/calendar.js.map +1 -0
- package/lib/components/checkbox.d.ts +189 -0
- package/lib/components/checkbox.d.ts.map +1 -0
- package/lib/components/checkbox.js +400 -0
- package/lib/components/checkbox.js.map +1 -0
- package/lib/components/copy.d.ts +180 -0
- package/lib/components/copy.d.ts.map +1 -0
- package/lib/components/copy.js +393 -0
- package/lib/components/copy.js.map +1 -0
- package/lib/components/date-picker.d.ts +379 -0
- package/lib/components/date-picker.d.ts.map +1 -0
- package/lib/components/date-picker.js +1586 -0
- package/lib/components/date-picker.js.map +1 -0
- package/lib/components/dropdown.d.ts +402 -0
- package/lib/components/dropdown.d.ts.map +1 -0
- package/lib/components/dropdown.js +1552 -0
- package/lib/components/dropdown.js.map +1 -0
- package/lib/components/icon.d.ts +118 -0
- package/lib/components/icon.d.ts.map +1 -0
- package/lib/components/icon.js +245 -0
- package/lib/components/icon.js.map +1 -0
- package/lib/components/input.d.ts +270 -0
- package/lib/components/input.d.ts.map +1 -0
- package/lib/components/input.js +721 -0
- package/lib/components/input.js.map +1 -0
- package/lib/components/modal.d.ts +58 -0
- package/lib/components/modal.d.ts.map +1 -0
- package/lib/components/modal.js +473 -0
- package/lib/components/modal.js.map +1 -0
- package/lib/components/multiselect.d.ts +397 -0
- package/lib/components/multiselect.d.ts.map +1 -0
- package/lib/components/multiselect.js +1580 -0
- package/lib/components/multiselect.js.map +1 -0
- package/lib/components/option.d.ts +66 -0
- package/lib/components/option.d.ts.map +1 -0
- package/lib/components/option.js +314 -0
- package/lib/components/option.js.map +1 -0
- package/lib/components/popup.d.ts +43 -0
- package/lib/components/popup.d.ts.map +1 -0
- package/lib/components/popup.js +380 -0
- package/lib/components/popup.js.map +1 -0
- package/lib/components/radio.d.ts +198 -0
- package/lib/components/radio.d.ts.map +1 -0
- package/lib/components/radio.js +437 -0
- package/lib/components/radio.js.map +1 -0
- package/lib/components/resize-observer.d.ts +48 -0
- package/lib/components/resize-observer.d.ts.map +1 -0
- package/lib/components/resize-observer.js +108 -0
- package/lib/components/resize-observer.js.map +1 -0
- package/lib/components/scroll-container.d.ts +51 -0
- package/lib/components/scroll-container.d.ts.map +1 -0
- package/lib/components/scroll-container.js +239 -0
- package/lib/components/scroll-container.js.map +1 -0
- package/lib/components/step.d.ts +26 -0
- package/lib/components/step.d.ts.map +1 -0
- package/lib/components/step.js +75 -0
- package/lib/components/step.js.map +1 -0
- package/lib/components/switch.d.ts +111 -0
- package/lib/components/switch.d.ts.map +1 -0
- package/lib/components/switch.js +240 -0
- package/lib/components/switch.js.map +1 -0
- package/lib/components/tab.d.ts +23 -0
- package/lib/components/tab.d.ts.map +1 -0
- package/lib/components/tab.js +76 -0
- package/lib/components/tab.js.map +1 -0
- package/lib/components/tabs.d.ts +93 -0
- package/lib/components/tabs.d.ts.map +1 -0
- package/lib/components/tabs.js +653 -0
- package/lib/components/tabs.js.map +1 -0
- package/lib/components/tag.d.ts +144 -0
- package/lib/components/tag.d.ts.map +1 -0
- package/lib/components/tag.js +316 -0
- package/lib/components/tag.js.map +1 -0
- package/lib/components/textarea.d.ts +241 -0
- package/lib/components/textarea.d.ts.map +1 -0
- package/lib/components/textarea.js +585 -0
- package/lib/components/textarea.js.map +1 -0
- package/lib/components/tooltip.d.ts +40 -0
- package/lib/components/tooltip.d.ts.map +1 -0
- package/lib/components/tooltip.js +439 -0
- package/lib/components/tooltip.js.map +1 -0
- package/lib/components/wizard.d.ts +86 -0
- package/lib/components/wizard.d.ts.map +1 -0
- package/lib/components/wizard.js +636 -0
- package/lib/components/wizard.js.map +1 -0
- package/lib/icons/fontawesome/brands.d.ts +557 -0
- package/lib/icons/fontawesome/brands.d.ts.map +1 -0
- package/lib/icons/fontawesome/brands.js +557 -0
- package/lib/icons/fontawesome/brands.js.map +1 -0
- package/lib/icons/fontawesome/regular.d.ts +281 -0
- package/lib/icons/fontawesome/regular.d.ts.map +1 -0
- package/lib/icons/fontawesome/regular.js +281 -0
- package/lib/icons/fontawesome/regular.js.map +1 -0
- package/lib/icons/fontawesome/solid.d.ts +1992 -0
- package/lib/icons/fontawesome/solid.d.ts.map +1 -0
- package/lib/icons/fontawesome/solid.js +1992 -0
- package/lib/icons/fontawesome/solid.js.map +1 -0
- package/lib/icons/heroicons/micro.d.ts +324 -0
- package/lib/icons/heroicons/micro.d.ts.map +1 -0
- package/lib/icons/heroicons/micro.js +1032 -0
- package/lib/icons/heroicons/micro.js.map +1 -0
- package/lib/icons/heroicons/mini.d.ts +332 -0
- package/lib/icons/heroicons/mini.d.ts.map +1 -0
- package/lib/icons/heroicons/mini.js +1038 -0
- package/lib/icons/heroicons/mini.js.map +1 -0
- package/lib/icons/heroicons/outline.d.ts +332 -0
- package/lib/icons/heroicons/outline.d.ts.map +1 -0
- package/lib/icons/heroicons/outline.js +993 -0
- package/lib/icons/heroicons/outline.js.map +1 -0
- package/lib/icons/heroicons/solid.d.ts +332 -0
- package/lib/icons/heroicons/solid.d.ts.map +1 -0
- package/lib/icons/heroicons/solid.js +1063 -0
- package/lib/icons/heroicons/solid.js.map +1 -0
- package/lib/icons/lucide.d.ts +1872 -0
- package/lib/icons/lucide.d.ts.map +1 -0
- package/lib/icons/lucide.js +28212 -0
- package/lib/icons/lucide.js.map +1 -0
- package/lib/icons/material/filled.d.ts +2180 -0
- package/lib/icons/material/filled.d.ts.map +1 -0
- package/lib/icons/material/filled.js +14003 -0
- package/lib/icons/material/filled.js.map +1 -0
- package/lib/icons/material/outlined.d.ts +2142 -0
- package/lib/icons/material/outlined.d.ts.map +1 -0
- package/lib/icons/material/outlined.js +14545 -0
- package/lib/icons/material/outlined.js.map +1 -0
- package/lib/icons/material/round.d.ts +2147 -0
- package/lib/icons/material/round.d.ts.map +1 -0
- package/lib/icons/material/round.js +14779 -0
- package/lib/icons/material/round.js.map +1 -0
- package/lib/icons/material/sharp.d.ts +2147 -0
- package/lib/icons/material/sharp.d.ts.map +1 -0
- package/lib/icons/material/sharp.js +14189 -0
- package/lib/icons/material/sharp.js.map +1 -0
- package/lib/icons/material/two-tone.d.ts +2185 -0
- package/lib/icons/material/two-tone.d.ts.map +1 -0
- package/lib/icons/material/two-tone.js +17152 -0
- package/lib/icons/material/two-tone.js.map +1 -0
- package/lib/index.d.ts +78 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +71 -0
- package/lib/index.js.map +1 -0
- package/lib/styles/button.d.ts +14 -0
- package/lib/styles/button.d.ts.map +1 -0
- package/lib/styles/button.js +457 -0
- package/lib/styles/button.js.map +1 -0
- package/lib/styles/calendar-month.d.ts +6 -0
- package/lib/styles/calendar-month.d.ts.map +1 -0
- package/lib/styles/calendar-month.js +275 -0
- package/lib/styles/calendar-month.js.map +1 -0
- package/lib/styles/calendar-navigation.d.ts +6 -0
- package/lib/styles/calendar-navigation.d.ts.map +1 -0
- package/lib/styles/calendar-navigation.js +143 -0
- package/lib/styles/calendar-navigation.js.map +1 -0
- package/lib/styles/calendar.d.ts +6 -0
- package/lib/styles/calendar.d.ts.map +1 -0
- package/lib/styles/calendar.js +28 -0
- package/lib/styles/calendar.js.map +1 -0
- package/lib/styles/checkbox.d.ts +9 -0
- package/lib/styles/checkbox.d.ts.map +1 -0
- package/lib/styles/checkbox.js +19 -0
- package/lib/styles/checkbox.js.map +1 -0
- package/lib/styles/copy.d.ts +7 -0
- package/lib/styles/copy.d.ts.map +1 -0
- package/lib/styles/copy.js +94 -0
- package/lib/styles/copy.js.map +1 -0
- package/lib/styles/custom-scrollbar.d.ts +6 -0
- package/lib/styles/custom-scrollbar.d.ts.map +1 -0
- package/lib/styles/custom-scrollbar.js +157 -0
- package/lib/styles/custom-scrollbar.js.map +1 -0
- package/lib/styles/date-picker.d.ts +6 -0
- package/lib/styles/date-picker.d.ts.map +1 -0
- package/lib/styles/date-picker.js +435 -0
- package/lib/styles/date-picker.js.map +1 -0
- package/lib/styles/dropdown.d.ts +12 -0
- package/lib/styles/dropdown.d.ts.map +1 -0
- package/lib/styles/dropdown.js +983 -0
- package/lib/styles/dropdown.js.map +1 -0
- package/lib/styles/icon.d.ts +6 -0
- package/lib/styles/icon.d.ts.map +1 -0
- package/lib/styles/icon.js +241 -0
- package/lib/styles/icon.js.map +1 -0
- package/lib/styles/input.d.ts +7 -0
- package/lib/styles/input.d.ts.map +1 -0
- package/lib/styles/input.js +685 -0
- package/lib/styles/input.js.map +1 -0
- package/lib/styles/modal.d.ts +8 -0
- package/lib/styles/modal.d.ts.map +1 -0
- package/lib/styles/modal.js +134 -0
- package/lib/styles/modal.js.map +1 -0
- package/lib/styles/multiselect.d.ts +6 -0
- package/lib/styles/multiselect.d.ts.map +1 -0
- package/lib/styles/multiselect.js +774 -0
- package/lib/styles/multiselect.js.map +1 -0
- package/lib/styles/option.d.ts +6 -0
- package/lib/styles/option.d.ts.map +1 -0
- package/lib/styles/option.js +116 -0
- package/lib/styles/option.js.map +1 -0
- package/lib/styles/popup.d.ts +8 -0
- package/lib/styles/popup.d.ts.map +1 -0
- package/lib/styles/popup.js +95 -0
- package/lib/styles/popup.js.map +1 -0
- package/lib/styles/radio.d.ts +8 -0
- package/lib/styles/radio.d.ts.map +1 -0
- package/lib/styles/radio.js +160 -0
- package/lib/styles/radio.js.map +1 -0
- package/lib/styles/resize-observer.d.ts +6 -0
- package/lib/styles/resize-observer.d.ts.map +1 -0
- package/lib/styles/resize-observer.js +18 -0
- package/lib/styles/resize-observer.js.map +1 -0
- package/lib/styles/scroll-container.d.ts +6 -0
- package/lib/styles/scroll-container.d.ts.map +1 -0
- package/lib/styles/scroll-container.js +198 -0
- package/lib/styles/scroll-container.js.map +1 -0
- package/lib/styles/step.d.ts +5 -0
- package/lib/styles/step.d.ts.map +1 -0
- package/lib/styles/step.js +50 -0
- package/lib/styles/step.js.map +1 -0
- package/lib/styles/switch.d.ts +9 -0
- package/lib/styles/switch.d.ts.map +1 -0
- package/lib/styles/switch.js +100 -0
- package/lib/styles/switch.js.map +1 -0
- package/lib/styles/tab.d.ts +5 -0
- package/lib/styles/tab.d.ts.map +1 -0
- package/lib/styles/tab.js +51 -0
- package/lib/styles/tab.js.map +1 -0
- package/lib/styles/tabs.d.ts +13 -0
- package/lib/styles/tabs.d.ts.map +1 -0
- package/lib/styles/tabs.js +184 -0
- package/lib/styles/tabs.js.map +1 -0
- package/lib/styles/tag.d.ts +6 -0
- package/lib/styles/tag.d.ts.map +1 -0
- package/lib/styles/tag.js +420 -0
- package/lib/styles/tag.js.map +1 -0
- package/lib/styles/textarea.d.ts +6 -0
- package/lib/styles/textarea.d.ts.map +1 -0
- package/lib/styles/textarea.js +350 -0
- package/lib/styles/textarea.js.map +1 -0
- package/lib/styles/tooltip.d.ts +9 -0
- package/lib/styles/tooltip.d.ts.map +1 -0
- package/lib/styles/tooltip.js +136 -0
- package/lib/styles/tooltip.js.map +1 -0
- package/lib/styles/wizard.d.ts +25 -0
- package/lib/styles/wizard.d.ts.map +1 -0
- package/lib/styles/wizard.js +325 -0
- package/lib/styles/wizard.js.map +1 -0
- package/lib/types/common.d.ts +143 -0
- package/lib/types/common.d.ts.map +1 -0
- package/lib/types/common.js +5 -0
- package/lib/types/common.js.map +1 -0
- package/lib/utils/calendar-utils.d.ts +176 -0
- package/lib/utils/calendar-utils.d.ts.map +1 -0
- package/lib/utils/calendar-utils.js +370 -0
- package/lib/utils/calendar-utils.js.map +1 -0
- package/lib/utils/custom-scrollbar.d.ts +82 -0
- package/lib/utils/custom-scrollbar.d.ts.map +1 -0
- package/lib/utils/custom-scrollbar.js +320 -0
- package/lib/utils/custom-scrollbar.js.map +1 -0
- package/lib/utils/icon-registry.d.ts +78 -0
- package/lib/utils/icon-registry.d.ts.map +1 -0
- package/lib/utils/icon-registry.js +304 -0
- package/lib/utils/icon-registry.js.map +1 -0
- package/lib/utils/locale.d.ts +136 -0
- package/lib/utils/locale.d.ts.map +1 -0
- package/lib/utils/locale.js +213 -0
- package/lib/utils/locale.js.map +1 -0
- package/lib/utils/mobile.d.ts +14 -0
- package/lib/utils/mobile.d.ts.map +1 -0
- package/lib/utils/mobile.js +21 -0
- package/lib/utils/mobile.js.map +1 -0
- package/lib/utils/number-format.d.ts +83 -0
- package/lib/utils/number-format.d.ts.map +1 -0
- package/lib/utils/number-format.js +143 -0
- package/lib/utils/number-format.js.map +1 -0
- package/lib/utils/parse-boolean.d.ts +39 -0
- package/lib/utils/parse-boolean.d.ts.map +1 -0
- package/lib/utils/parse-boolean.js +58 -0
- package/lib/utils/parse-boolean.js.map +1 -0
- package/lib/utils/positioning.d.ts +143 -0
- package/lib/utils/positioning.d.ts.map +1 -0
- package/lib/utils/positioning.js +308 -0
- package/lib/utils/positioning.js.map +1 -0
- package/lib/utils/property-capture.d.ts +132 -0
- package/lib/utils/property-capture.d.ts.map +1 -0
- package/lib/utils/property-capture.js +152 -0
- package/lib/utils/property-capture.js.map +1 -0
- package/lib/utils/property-manager.d.ts +90 -0
- package/lib/utils/property-manager.d.ts.map +1 -0
- package/lib/utils/property-manager.js +197 -0
- package/lib/utils/property-manager.js.map +1 -0
- package/lib/utils/resize-observer.d.ts +42 -0
- package/lib/utils/resize-observer.d.ts.map +1 -0
- package/lib/utils/resize-observer.js +71 -0
- package/lib/utils/resize-observer.js.map +1 -0
- package/lib/utils/scroll-lock.d.ts +79 -0
- package/lib/utils/scroll-lock.d.ts.map +1 -0
- package/lib/utils/scroll-lock.js +197 -0
- package/lib/utils/scroll-lock.js.map +1 -0
- package/lib/utils/styles.d.ts +27 -0
- package/lib/utils/styles.d.ts.map +1 -0
- package/lib/utils/styles.js +53 -0
- package/lib/utils/styles.js.map +1 -0
- package/lib/version.d.ts +8 -0
- package/lib/version.d.ts.map +1 -0
- package/lib/version.js +11 -0
- package/lib/version.js.map +1 -0
- package/package.json +159 -0
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TyInput Web Component
|
|
3
|
+
* PORTED FROM: clj/ty/components/input.cljs
|
|
4
|
+
* Phase D: Complete with debounce feature for input and change events
|
|
5
|
+
*
|
|
6
|
+
* Enhanced input component with:
|
|
7
|
+
* - Label, error messages, semantic styling
|
|
8
|
+
* - Icon slots (start/end)
|
|
9
|
+
* - Numeric formatting with shadow values
|
|
10
|
+
* - Currency, percent, compact notation
|
|
11
|
+
* - Format-on-blur / raw-on-focus behavior
|
|
12
|
+
* - Debounce (0-5000ms) for input/change events
|
|
13
|
+
* - Immediate event firing on blur (cancels pending debounce)
|
|
14
|
+
*
|
|
15
|
+
* NOTE: Checkbox functionality is in separate ty-checkbox component
|
|
16
|
+
*/
|
|
17
|
+
import { TyComponent } from '../base/ty-component.js';
|
|
18
|
+
import { ensureStyles } from '../utils/styles.js';
|
|
19
|
+
import { inputStyles } from '../styles/input.js';
|
|
20
|
+
import { formatNumber, parseNumericValue, shouldFormat as shouldFormatType } from '../utils/number-format.js';
|
|
21
|
+
import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
|
|
22
|
+
/**
|
|
23
|
+
* Required indicator SVG icon (from Lucide)
|
|
24
|
+
*/
|
|
25
|
+
const REQUIRED_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-asterisk-icon lucide-asterisk"><path d="M12 6v12"/><path d="M17.196 9 6.804 15"/><path d="m6.804 9 10.392 6"/></svg>`;
|
|
26
|
+
export class TyInput extends TyComponent {
|
|
27
|
+
constructor() {
|
|
28
|
+
super(); // TyComponent handles attachInternals() and attachShadow()
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// INTERNAL STATE - Not managed by PropertyManager
|
|
31
|
+
// NOTE: _internals provided by TyComponent base class
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Shadow value - parsed numeric value for formatting
|
|
34
|
+
this._shadowValue = null;
|
|
35
|
+
// Focus state - toggles format/raw display
|
|
36
|
+
this._isFocused = false;
|
|
37
|
+
// Listener setup tracking
|
|
38
|
+
this._listenersSetup = false;
|
|
39
|
+
// Store references to handlers for cleanup
|
|
40
|
+
this._inputHandler = null;
|
|
41
|
+
this._changeHandler = null;
|
|
42
|
+
this._focusHandler = null;
|
|
43
|
+
this._blurHandler = null;
|
|
44
|
+
// Debounce timers
|
|
45
|
+
this._inputDebounceTimer = null;
|
|
46
|
+
this._changeDebounceTimer = null;
|
|
47
|
+
// Apply styles to shadow root
|
|
48
|
+
const shadow = this.shadowRoot;
|
|
49
|
+
ensureStyles(shadow, { css: inputStyles, id: 'ty-input' });
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Called when component connects to DOM
|
|
53
|
+
* TyComponent already handled pre-connection property capture
|
|
54
|
+
*/
|
|
55
|
+
onConnect() {
|
|
56
|
+
// Initialize shadow value from current value property
|
|
57
|
+
this.initializeShadowValue();
|
|
58
|
+
// NOTE: Event listeners are set up in render() after input element is created
|
|
59
|
+
// setupEventListeners() will be called after initial render
|
|
60
|
+
// Setup locale observer to watch for ancestor lang changes
|
|
61
|
+
this._localeObserver = observeLocaleChanges(this, () => {
|
|
62
|
+
this.render();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Called when component disconnects from DOM
|
|
67
|
+
*/
|
|
68
|
+
onDisconnect() {
|
|
69
|
+
// Clean up event listeners
|
|
70
|
+
this.removeEventListeners();
|
|
71
|
+
// Cleanup locale observer
|
|
72
|
+
if (this._localeObserver) {
|
|
73
|
+
this._localeObserver();
|
|
74
|
+
this._localeObserver = undefined;
|
|
75
|
+
}
|
|
76
|
+
// Clear any pending debounce timers
|
|
77
|
+
if (this._inputDebounceTimer !== null) {
|
|
78
|
+
clearTimeout(this._inputDebounceTimer);
|
|
79
|
+
this._inputDebounceTimer = null;
|
|
80
|
+
}
|
|
81
|
+
if (this._changeDebounceTimer !== null) {
|
|
82
|
+
clearTimeout(this._changeDebounceTimer);
|
|
83
|
+
this._changeDebounceTimer = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// TYCOMPONENT LIFECYCLE HOOKS
|
|
88
|
+
// ============================================================================
|
|
89
|
+
/**
|
|
90
|
+
* Handle property changes - called BEFORE render
|
|
91
|
+
* This replaces the old attributeChangedCallback logic
|
|
92
|
+
*/
|
|
93
|
+
onPropertiesChanged(changes) {
|
|
94
|
+
for (const { name, newValue } of changes) {
|
|
95
|
+
switch (name) {
|
|
96
|
+
case 'value':
|
|
97
|
+
// Parse to shadow value for numeric types
|
|
98
|
+
this._shadowValue = this.parseShadowValue(newValue || '');
|
|
99
|
+
break;
|
|
100
|
+
case 'type':
|
|
101
|
+
// Re-parse shadow value when type changes
|
|
102
|
+
const currentValue = this.getProperty('value') || '';
|
|
103
|
+
this._shadowValue = this.parseShadowValue(currentValue);
|
|
104
|
+
break;
|
|
105
|
+
case 'error':
|
|
106
|
+
// Auto-set flavor to danger when error is present and flavor is neutral
|
|
107
|
+
if (newValue && this.getProperty('flavor') === 'neutral') {
|
|
108
|
+
// Use setProperty to trigger proper lifecycle
|
|
109
|
+
this.setProperty('flavor', 'danger');
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Hook: Called when form is reset
|
|
117
|
+
* Clear shadow value and focus state
|
|
118
|
+
*/
|
|
119
|
+
onFormReset() {
|
|
120
|
+
// Reset shadow value to default (empty)
|
|
121
|
+
this._shadowValue = null;
|
|
122
|
+
// Reset focus state
|
|
123
|
+
this._isFocused = false;
|
|
124
|
+
// Clear any pending debounce timers
|
|
125
|
+
if (this._inputDebounceTimer !== null) {
|
|
126
|
+
clearTimeout(this._inputDebounceTimer);
|
|
127
|
+
this._inputDebounceTimer = null;
|
|
128
|
+
}
|
|
129
|
+
if (this._changeDebounceTimer !== null) {
|
|
130
|
+
clearTimeout(this._changeDebounceTimer);
|
|
131
|
+
this._changeDebounceTimer = null;
|
|
132
|
+
}
|
|
133
|
+
this.render();
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Override form value to return shadow value
|
|
137
|
+
* This ensures numeric types submit their parsed values
|
|
138
|
+
*/
|
|
139
|
+
getFormValue() {
|
|
140
|
+
const formValue = this._shadowValue !== null ? String(this._shadowValue) : null;
|
|
141
|
+
return formValue;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Initialize shadow value from the initial value attribute
|
|
145
|
+
*/
|
|
146
|
+
initializeShadowValue() {
|
|
147
|
+
const currentValue = this.getProperty('value');
|
|
148
|
+
if (currentValue) {
|
|
149
|
+
this._shadowValue = this.parseShadowValue(currentValue);
|
|
150
|
+
// Update form value
|
|
151
|
+
this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : null);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Parse a string value to the appropriate shadow value type
|
|
156
|
+
*/
|
|
157
|
+
parseShadowValue(value) {
|
|
158
|
+
// Defensive check: ensure value is actually a string before calling .trim()
|
|
159
|
+
if (!value || typeof value !== 'string' || value.trim() === '')
|
|
160
|
+
return null;
|
|
161
|
+
// For numeric types, parse to number
|
|
162
|
+
if (shouldFormatType(this.type)) {
|
|
163
|
+
const parsed = parseNumericValue(value);
|
|
164
|
+
if (parsed !== null && this.precision === 0) {
|
|
165
|
+
return Math.round(parsed);
|
|
166
|
+
}
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
// For other types, keep as string
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Check if current input should format numbers
|
|
174
|
+
*/
|
|
175
|
+
shouldFormat() {
|
|
176
|
+
return shouldFormatType(this.type) &&
|
|
177
|
+
!this._isFocused &&
|
|
178
|
+
this._shadowValue !== null &&
|
|
179
|
+
typeof this._shadowValue === 'number';
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the format configuration for current input
|
|
183
|
+
*/
|
|
184
|
+
getFormatConfig() {
|
|
185
|
+
return {
|
|
186
|
+
type: this.type,
|
|
187
|
+
locale: this.locale, // Use getter which calls getEffectiveLocale correctly
|
|
188
|
+
currency: this.currency,
|
|
189
|
+
precision: this.precision
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get the display value (formatted or raw)
|
|
194
|
+
*/
|
|
195
|
+
getDisplayValue() {
|
|
196
|
+
if (this.shouldFormat()) {
|
|
197
|
+
let valueToFormat = this._shadowValue;
|
|
198
|
+
// For percent: divide by 100 (user enters 15, displays as 15%)
|
|
199
|
+
// This matches ClojureScript behavior
|
|
200
|
+
if (this.type === 'percent') {
|
|
201
|
+
valueToFormat = valueToFormat / 100;
|
|
202
|
+
}
|
|
203
|
+
return formatNumber(valueToFormat, this.getFormatConfig());
|
|
204
|
+
}
|
|
205
|
+
return this._shadowValue !== null ? String(this._shadowValue) : '';
|
|
206
|
+
}
|
|
207
|
+
// ============================================================================
|
|
208
|
+
// PROPERTY ACCESSORS - Simplified with TyComponent
|
|
209
|
+
// NOTE: validateFlavor removed - now handled by property configuration
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Core properties - simple getProperty/setProperty pattern
|
|
212
|
+
get type() { return this.getProperty('type'); }
|
|
213
|
+
set type(v) { this.setProperty('type', v); }
|
|
214
|
+
// Value - special getter returns shadow value
|
|
215
|
+
get value() {
|
|
216
|
+
return this._shadowValue !== null ? String(this._shadowValue) : '';
|
|
217
|
+
}
|
|
218
|
+
set value(v) { this.setProperty('value', v); }
|
|
219
|
+
get name() { return this.getProperty('name'); }
|
|
220
|
+
set name(v) { this.setProperty('name', v); }
|
|
221
|
+
get placeholder() { return this.getProperty('placeholder'); }
|
|
222
|
+
set placeholder(v) { this.setProperty('placeholder', v); }
|
|
223
|
+
get label() { return this.getProperty('label'); }
|
|
224
|
+
set label(v) { this.setProperty('label', v); }
|
|
225
|
+
get disabled() { return this.getProperty('disabled'); }
|
|
226
|
+
set disabled(v) { this.setProperty('disabled', v); }
|
|
227
|
+
get required() { return this.getProperty('required'); }
|
|
228
|
+
set required(v) { this.setProperty('required', v); }
|
|
229
|
+
get error() { return this.getProperty('error'); }
|
|
230
|
+
set error(v) { this.setProperty('error', v); }
|
|
231
|
+
get size() { return this.getProperty('size'); }
|
|
232
|
+
set size(v) { this.setProperty('size', v); }
|
|
233
|
+
get flavor() { return this.getProperty('flavor'); }
|
|
234
|
+
set flavor(v) { this.setProperty('flavor', v); }
|
|
235
|
+
// Numeric formatting properties
|
|
236
|
+
get currency() { return this.getProperty('currency'); }
|
|
237
|
+
set currency(v) { this.setProperty('currency', v); }
|
|
238
|
+
// Locale - special getter uses getEffectiveLocale()
|
|
239
|
+
get locale() {
|
|
240
|
+
return getEffectiveLocale(this, this.getProperty('locale'));
|
|
241
|
+
}
|
|
242
|
+
set locale(v) { this.setProperty('locale', v); }
|
|
243
|
+
get precision() { return this.getProperty('precision'); }
|
|
244
|
+
set precision(v) { this.setProperty('precision', v); }
|
|
245
|
+
// Debounce property
|
|
246
|
+
get debounce() { return this.getProperty('debounce'); }
|
|
247
|
+
set debounce(v) { this.setProperty('debounce', v); }
|
|
248
|
+
// Form property
|
|
249
|
+
get form() {
|
|
250
|
+
return this._internals.form;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Build CSS class list for input wrapper
|
|
254
|
+
*/
|
|
255
|
+
buildClassList() {
|
|
256
|
+
const classes = [this.size, this.flavor];
|
|
257
|
+
if (this.disabled)
|
|
258
|
+
classes.push('disabled');
|
|
259
|
+
if (this.required)
|
|
260
|
+
classes.push('required');
|
|
261
|
+
if (this.error)
|
|
262
|
+
classes.push('error');
|
|
263
|
+
return classes.join(' ');
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Dispatch input event (helper method for debouncing)
|
|
267
|
+
*/
|
|
268
|
+
dispatchInputEvent(rawValue, originalEvent) {
|
|
269
|
+
this.dispatchEvent(new CustomEvent('input', {
|
|
270
|
+
detail: {
|
|
271
|
+
value: this._shadowValue,
|
|
272
|
+
formattedValue: this.shouldFormat() ? this.getDisplayValue() : null,
|
|
273
|
+
rawValue: rawValue,
|
|
274
|
+
originalEvent: originalEvent
|
|
275
|
+
},
|
|
276
|
+
bubbles: true,
|
|
277
|
+
composed: true
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Dispatch change event (helper method for debouncing)
|
|
282
|
+
*/
|
|
283
|
+
dispatchChangeEvent(rawValue, originalEvent) {
|
|
284
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
285
|
+
detail: {
|
|
286
|
+
value: this._shadowValue,
|
|
287
|
+
formattedValue: this.shouldFormat() ? this.getDisplayValue() : null,
|
|
288
|
+
rawValue: rawValue,
|
|
289
|
+
originalEvent: originalEvent
|
|
290
|
+
},
|
|
291
|
+
bubbles: true,
|
|
292
|
+
composed: true
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Remove event listeners for cleanup
|
|
297
|
+
*/
|
|
298
|
+
removeEventListeners() {
|
|
299
|
+
if (!this._listenersSetup)
|
|
300
|
+
return;
|
|
301
|
+
const shadow = this.shadowRoot;
|
|
302
|
+
if (!shadow)
|
|
303
|
+
return;
|
|
304
|
+
const inputEl = shadow.querySelector('input');
|
|
305
|
+
if (!inputEl)
|
|
306
|
+
return;
|
|
307
|
+
// Remove all event listeners using stored handler references
|
|
308
|
+
if (this._inputHandler) {
|
|
309
|
+
inputEl.removeEventListener('input', this._inputHandler);
|
|
310
|
+
this._inputHandler = null;
|
|
311
|
+
}
|
|
312
|
+
if (this._changeHandler) {
|
|
313
|
+
inputEl.removeEventListener('change', this._changeHandler);
|
|
314
|
+
this._changeHandler = null;
|
|
315
|
+
}
|
|
316
|
+
if (this._focusHandler) {
|
|
317
|
+
inputEl.removeEventListener('focus', this._focusHandler);
|
|
318
|
+
this._focusHandler = null;
|
|
319
|
+
}
|
|
320
|
+
if (this._blurHandler) {
|
|
321
|
+
inputEl.removeEventListener('blur', this._blurHandler);
|
|
322
|
+
this._blurHandler = null;
|
|
323
|
+
}
|
|
324
|
+
this._listenersSetup = false;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Setup event listeners for input element
|
|
328
|
+
* IMPORTANT: Only called ONCE, not on every render (like ClojureScript)
|
|
329
|
+
*/
|
|
330
|
+
setupEventListeners() {
|
|
331
|
+
if (this._listenersSetup) {
|
|
332
|
+
return; // Already setup
|
|
333
|
+
}
|
|
334
|
+
const shadow = this.shadowRoot;
|
|
335
|
+
const inputEl = shadow.querySelector('input');
|
|
336
|
+
const wrapperEl = shadow.querySelector('.input-wrapper');
|
|
337
|
+
if (!inputEl || !wrapperEl) {
|
|
338
|
+
console.warn('[TyInput.setupEventListeners] ⚠️ Input element not found!');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Create and store handler references for cleanup
|
|
342
|
+
// Input event - update shadow value and form (with debounce support)
|
|
343
|
+
this._inputHandler = (e) => {
|
|
344
|
+
// Stop native event from propagating - only our custom event should bubble
|
|
345
|
+
e.stopPropagation();
|
|
346
|
+
e.stopImmediatePropagation();
|
|
347
|
+
const target = e.target;
|
|
348
|
+
const rawValue = target.value;
|
|
349
|
+
// Parse to shadow value
|
|
350
|
+
this._shadowValue = this.parseShadowValue(rawValue);
|
|
351
|
+
// Update form value with shadow value
|
|
352
|
+
this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : null);
|
|
353
|
+
// Clear existing timer
|
|
354
|
+
if (this._inputDebounceTimer !== null) {
|
|
355
|
+
clearTimeout(this._inputDebounceTimer);
|
|
356
|
+
}
|
|
357
|
+
// If debounce is set, debounce the event
|
|
358
|
+
if (this.debounce > 0) {
|
|
359
|
+
this._inputDebounceTimer = window.setTimeout(() => {
|
|
360
|
+
// Use current value, not the captured rawValue
|
|
361
|
+
this.dispatchInputEvent(inputEl.value, e);
|
|
362
|
+
this._inputDebounceTimer = null;
|
|
363
|
+
}, this.debounce);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
// Fire immediately if no debounce
|
|
367
|
+
this.dispatchInputEvent(rawValue, e);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
// Change event (with debounce support)
|
|
371
|
+
this._changeHandler = (e) => {
|
|
372
|
+
// Stop native event from propagating - only our custom event should bubble
|
|
373
|
+
e.stopPropagation();
|
|
374
|
+
e.stopImmediatePropagation();
|
|
375
|
+
const target = e.target;
|
|
376
|
+
const rawValue = target.value;
|
|
377
|
+
// Parse to shadow value
|
|
378
|
+
this._shadowValue = this.parseShadowValue(rawValue);
|
|
379
|
+
// Update form value
|
|
380
|
+
this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : null);
|
|
381
|
+
// Clear existing timer
|
|
382
|
+
if (this._changeDebounceTimer !== null) {
|
|
383
|
+
clearTimeout(this._changeDebounceTimer);
|
|
384
|
+
}
|
|
385
|
+
// If debounce is set, debounce the event
|
|
386
|
+
if (this.debounce > 0) {
|
|
387
|
+
this._changeDebounceTimer = window.setTimeout(() => {
|
|
388
|
+
// Use current value, not the captured rawValue
|
|
389
|
+
this.dispatchChangeEvent(inputEl.value, e);
|
|
390
|
+
this._changeDebounceTimer = null;
|
|
391
|
+
}, this.debounce);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
// Fire immediately if no debounce
|
|
395
|
+
this.dispatchChangeEvent(rawValue, e);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
// Focus event - show raw value for numeric types
|
|
399
|
+
this._focusHandler = (e) => {
|
|
400
|
+
this._isFocused = true;
|
|
401
|
+
wrapperEl.classList.add('focused');
|
|
402
|
+
// For numeric types, show raw shadow value
|
|
403
|
+
if (shouldFormatType(this.type) && this._shadowValue !== null) {
|
|
404
|
+
inputEl.value = String(this._shadowValue);
|
|
405
|
+
}
|
|
406
|
+
this.dispatchEvent(new CustomEvent('focus', {
|
|
407
|
+
detail: { originalEvent: e },
|
|
408
|
+
bubbles: true,
|
|
409
|
+
composed: true
|
|
410
|
+
}));
|
|
411
|
+
};
|
|
412
|
+
// Blur event - fire pending debounced events immediately, then show formatted value
|
|
413
|
+
this._blurHandler = (e) => {
|
|
414
|
+
const target = e.target;
|
|
415
|
+
const rawValue = target.value;
|
|
416
|
+
// Fire any pending debounced input event immediately on blur
|
|
417
|
+
if (this._inputDebounceTimer !== null) {
|
|
418
|
+
clearTimeout(this._inputDebounceTimer);
|
|
419
|
+
this._inputDebounceTimer = null;
|
|
420
|
+
this.dispatchInputEvent(rawValue, e);
|
|
421
|
+
}
|
|
422
|
+
// Fire any pending debounced change event immediately on blur
|
|
423
|
+
if (this._changeDebounceTimer !== null) {
|
|
424
|
+
clearTimeout(this._changeDebounceTimer);
|
|
425
|
+
this._changeDebounceTimer = null;
|
|
426
|
+
this.dispatchChangeEvent(rawValue, e);
|
|
427
|
+
}
|
|
428
|
+
this._isFocused = false;
|
|
429
|
+
wrapperEl.classList.remove('focused');
|
|
430
|
+
// For numeric types, show formatted value
|
|
431
|
+
if (this.shouldFormat()) {
|
|
432
|
+
inputEl.value = this.getDisplayValue();
|
|
433
|
+
}
|
|
434
|
+
this.dispatchEvent(new CustomEvent('blur', {
|
|
435
|
+
detail: { originalEvent: e },
|
|
436
|
+
bubbles: true,
|
|
437
|
+
composed: true
|
|
438
|
+
}));
|
|
439
|
+
};
|
|
440
|
+
// Add event listeners
|
|
441
|
+
inputEl.addEventListener('input', this._inputHandler);
|
|
442
|
+
inputEl.addEventListener('change', this._changeHandler);
|
|
443
|
+
inputEl.addEventListener('focus', this._focusHandler);
|
|
444
|
+
inputEl.addEventListener('blur', this._blurHandler);
|
|
445
|
+
this._listenersSetup = true;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Render the input component with wrapper and slots
|
|
449
|
+
* Phase C: Uses getDisplayValue() for formatted output
|
|
450
|
+
*
|
|
451
|
+
*/
|
|
452
|
+
render() {
|
|
453
|
+
const shadow = this.shadowRoot;
|
|
454
|
+
const existingInput = shadow.querySelector('input');
|
|
455
|
+
const existingWrapper = shadow.querySelector('.input-wrapper');
|
|
456
|
+
const existingLabel = shadow.querySelector('.input-label');
|
|
457
|
+
const existingError = shadow.querySelector('.error-message');
|
|
458
|
+
const classes = this.buildClassList();
|
|
459
|
+
// Map input type (all numeric types use 'text' in DOM, others pass through)
|
|
460
|
+
const inputType = ['password', 'email', 'tel', 'url'].includes(this.type)
|
|
461
|
+
? this.type
|
|
462
|
+
: 'text';
|
|
463
|
+
// Set inputmode for mobile keyboard hint
|
|
464
|
+
const inputMode = shouldFormatType(this.type) ? 'decimal'
|
|
465
|
+
: this.type === 'email' ? 'email'
|
|
466
|
+
: this.type === 'tel' ? 'tel'
|
|
467
|
+
: this.type === 'url' ? 'url'
|
|
468
|
+
: undefined;
|
|
469
|
+
// Get display value (formatted or raw based on focus)
|
|
470
|
+
const displayValue = this.getDisplayValue();
|
|
471
|
+
// If input exists, just update properties (like ClojureScript does)
|
|
472
|
+
if (existingInput && existingWrapper) {
|
|
473
|
+
existingInput.type = inputType;
|
|
474
|
+
existingInput.value = displayValue;
|
|
475
|
+
existingInput.placeholder = this.placeholder;
|
|
476
|
+
existingInput.name = this.name;
|
|
477
|
+
if (inputMode)
|
|
478
|
+
existingInput.inputMode = inputMode;
|
|
479
|
+
else
|
|
480
|
+
existingInput.removeAttribute('inputmode');
|
|
481
|
+
// Update wrapper classes
|
|
482
|
+
existingWrapper.className = `input-wrapper ${classes}`;
|
|
483
|
+
// Set disabled property AND manage attribute
|
|
484
|
+
existingInput.disabled = this.disabled;
|
|
485
|
+
if (this.disabled) {
|
|
486
|
+
existingInput.setAttribute('disabled', '');
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
existingInput.removeAttribute('disabled');
|
|
490
|
+
}
|
|
491
|
+
// Set required property AND manage attribute
|
|
492
|
+
existingInput.required = this.required;
|
|
493
|
+
if (this.required) {
|
|
494
|
+
existingInput.setAttribute('required', '');
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
existingInput.removeAttribute('required');
|
|
498
|
+
}
|
|
499
|
+
// ===== BUG FIX: Dynamic label creation =====
|
|
500
|
+
// Update or CREATE label if needed
|
|
501
|
+
if (this.label) {
|
|
502
|
+
if (existingLabel) {
|
|
503
|
+
// Label exists, just update it
|
|
504
|
+
existingLabel.innerHTML = `${this.label}${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}`;
|
|
505
|
+
existingLabel.style.display = 'flex';
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
// Label doesn't exist but we need one - CREATE IT!
|
|
509
|
+
const labelEl = document.createElement('label');
|
|
510
|
+
labelEl.className = 'input-label';
|
|
511
|
+
labelEl.innerHTML = `${this.label}${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}`;
|
|
512
|
+
// Insert label BEFORE the input-wrapper
|
|
513
|
+
const container = shadow.querySelector('.input-container');
|
|
514
|
+
if (container) {
|
|
515
|
+
container.insertBefore(labelEl, existingWrapper);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else if (existingLabel) {
|
|
520
|
+
// No label text, hide existing label
|
|
521
|
+
;
|
|
522
|
+
existingLabel.style.display = 'none';
|
|
523
|
+
}
|
|
524
|
+
// Update error message
|
|
525
|
+
if (this.error) {
|
|
526
|
+
if (existingError) {
|
|
527
|
+
existingError.textContent = this.error;
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
const errorEl = document.createElement('div');
|
|
531
|
+
errorEl.className = 'error-message';
|
|
532
|
+
errorEl.textContent = this.error;
|
|
533
|
+
shadow.querySelector('.input-container')?.appendChild(errorEl);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
else if (existingError) {
|
|
537
|
+
existingError.remove();
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
// Create initial structure with wrapper and slots
|
|
542
|
+
const labelHtml = this.label ? `
|
|
543
|
+
<label class="input-label">
|
|
544
|
+
${this.label}
|
|
545
|
+
${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}
|
|
546
|
+
</label>
|
|
547
|
+
` : '';
|
|
548
|
+
const errorHtml = this.error ? `
|
|
549
|
+
<div class="error-message">${this.error}</div>
|
|
550
|
+
` : '';
|
|
551
|
+
shadow.innerHTML = `
|
|
552
|
+
<div class="input-container">
|
|
553
|
+
${labelHtml}
|
|
554
|
+
<div class="input-wrapper ${classes}">
|
|
555
|
+
<slot name="start"></slot>
|
|
556
|
+
<input
|
|
557
|
+
type="${inputType}"
|
|
558
|
+
value="${displayValue}"
|
|
559
|
+
placeholder="${this.placeholder}"
|
|
560
|
+
name="${this.name}"
|
|
561
|
+
${inputMode ? `inputmode="${inputMode}"` : ''}
|
|
562
|
+
/>
|
|
563
|
+
<slot name="end"></slot>
|
|
564
|
+
</div>
|
|
565
|
+
${errorHtml}
|
|
566
|
+
</div>
|
|
567
|
+
`;
|
|
568
|
+
// Set boolean properties after creating element
|
|
569
|
+
const inputEl = shadow.querySelector('input');
|
|
570
|
+
inputEl.disabled = this.disabled;
|
|
571
|
+
inputEl.required = this.required;
|
|
572
|
+
// Setup event listeners ONCE
|
|
573
|
+
this.setupEventListeners();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
TyInput.formAssociated = true;
|
|
578
|
+
// ============================================================================
|
|
579
|
+
// PROPERTY CONFIGURATION - Declarative property lifecycle
|
|
580
|
+
// ============================================================================
|
|
581
|
+
TyInput.properties = {
|
|
582
|
+
// Core properties
|
|
583
|
+
type: {
|
|
584
|
+
type: 'string',
|
|
585
|
+
visual: true,
|
|
586
|
+
default: 'text',
|
|
587
|
+
validate: (v) => {
|
|
588
|
+
const validTypes = [
|
|
589
|
+
'text', 'email', 'password', 'number', 'tel', 'url',
|
|
590
|
+
'currency', 'percent', 'compact'
|
|
591
|
+
];
|
|
592
|
+
return validTypes.includes(v);
|
|
593
|
+
},
|
|
594
|
+
coerce: (v) => {
|
|
595
|
+
const validTypes = [
|
|
596
|
+
'text', 'email', 'password', 'number', 'tel', 'url',
|
|
597
|
+
'currency', 'percent', 'compact'
|
|
598
|
+
];
|
|
599
|
+
if (!validTypes.includes(v)) {
|
|
600
|
+
console.warn(`[ty-input] Invalid type '${v}'. Using 'text'.`);
|
|
601
|
+
return 'text';
|
|
602
|
+
}
|
|
603
|
+
return v;
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
value: {
|
|
607
|
+
type: 'string',
|
|
608
|
+
visual: true,
|
|
609
|
+
formValue: true,
|
|
610
|
+
emitChange: true,
|
|
611
|
+
default: ''
|
|
612
|
+
},
|
|
613
|
+
name: {
|
|
614
|
+
type: 'string',
|
|
615
|
+
default: ''
|
|
616
|
+
},
|
|
617
|
+
placeholder: {
|
|
618
|
+
type: 'string',
|
|
619
|
+
visual: true,
|
|
620
|
+
default: ''
|
|
621
|
+
},
|
|
622
|
+
label: {
|
|
623
|
+
type: 'string',
|
|
624
|
+
visual: true,
|
|
625
|
+
default: ''
|
|
626
|
+
},
|
|
627
|
+
disabled: {
|
|
628
|
+
type: 'boolean',
|
|
629
|
+
visual: true,
|
|
630
|
+
default: false
|
|
631
|
+
},
|
|
632
|
+
required: {
|
|
633
|
+
type: 'boolean',
|
|
634
|
+
visual: true,
|
|
635
|
+
default: false
|
|
636
|
+
},
|
|
637
|
+
error: {
|
|
638
|
+
type: 'string',
|
|
639
|
+
visual: true,
|
|
640
|
+
default: ''
|
|
641
|
+
},
|
|
642
|
+
size: {
|
|
643
|
+
type: 'string',
|
|
644
|
+
visual: true,
|
|
645
|
+
default: 'md',
|
|
646
|
+
validate: (v) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(v),
|
|
647
|
+
coerce: (v) => {
|
|
648
|
+
if (!['xs', 'sm', 'md', 'lg', 'xl'].includes(v)) {
|
|
649
|
+
console.warn(`[ty-input] Invalid size '${v}'. Using 'md'.`);
|
|
650
|
+
return 'md';
|
|
651
|
+
}
|
|
652
|
+
return v;
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
flavor: {
|
|
656
|
+
type: 'string',
|
|
657
|
+
visual: true,
|
|
658
|
+
default: 'neutral',
|
|
659
|
+
validate: (v) => {
|
|
660
|
+
const valid = ['primary', 'secondary', 'success', 'danger', 'warning', 'neutral'];
|
|
661
|
+
return valid.includes(v);
|
|
662
|
+
},
|
|
663
|
+
coerce: (v) => {
|
|
664
|
+
const valid = ['primary', 'secondary', 'success', 'danger', 'warning', 'neutral'];
|
|
665
|
+
if (!valid.includes(v)) {
|
|
666
|
+
console.warn(`[ty-input] Invalid flavor '${v}'. Using 'neutral'.`);
|
|
667
|
+
return 'neutral';
|
|
668
|
+
}
|
|
669
|
+
return v;
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
// Numeric formatting properties
|
|
673
|
+
currency: {
|
|
674
|
+
type: 'string',
|
|
675
|
+
visual: true,
|
|
676
|
+
default: 'USD'
|
|
677
|
+
},
|
|
678
|
+
locale: {
|
|
679
|
+
type: 'string',
|
|
680
|
+
visual: true,
|
|
681
|
+
default: 'en-US'
|
|
682
|
+
},
|
|
683
|
+
precision: {
|
|
684
|
+
type: 'number',
|
|
685
|
+
visual: true,
|
|
686
|
+
default: undefined,
|
|
687
|
+
validate: (v) => {
|
|
688
|
+
if (v === undefined || v === null)
|
|
689
|
+
return true;
|
|
690
|
+
return typeof v === 'number' && v >= 0 && v <= 10;
|
|
691
|
+
},
|
|
692
|
+
coerce: (v) => {
|
|
693
|
+
if (v === undefined || v === null)
|
|
694
|
+
return undefined;
|
|
695
|
+
const num = Number(v);
|
|
696
|
+
if (isNaN(num))
|
|
697
|
+
return undefined;
|
|
698
|
+
return Math.max(0, Math.min(10, Math.floor(num)));
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
// Debounce property
|
|
702
|
+
debounce: {
|
|
703
|
+
type: 'number',
|
|
704
|
+
default: 0,
|
|
705
|
+
validate: (v) => {
|
|
706
|
+
const num = Number(v);
|
|
707
|
+
return !isNaN(num) && num >= 0 && num <= 5000;
|
|
708
|
+
},
|
|
709
|
+
coerce: (v) => {
|
|
710
|
+
const num = Number(v);
|
|
711
|
+
if (isNaN(num))
|
|
712
|
+
return 0;
|
|
713
|
+
return Math.max(0, Math.min(5000, Math.floor(num)));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
// Register the custom element
|
|
718
|
+
if (!customElements.get('ty-input')) {
|
|
719
|
+
customElements.define('ty-input', TyInput);
|
|
720
|
+
}
|
|
721
|
+
//# sourceMappingURL=input.js.map
|