twintrinsic 0.0.1
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 +674 -0
- package/README.md +150 -0
- package/dist/App/App.svelte +54 -0
- package/dist/App/App.svelte.d.ts +65 -0
- package/dist/Section.svelte +25 -0
- package/dist/Section.svelte.d.ts +34 -0
- package/dist/actions/clickOutside.d.ts +9 -0
- package/dist/actions/clickOutside.js +19 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/components/Accordion/Accordion.svelte +75 -0
- package/dist/components/Accordion/Accordion.svelte.d.ts +39 -0
- package/dist/components/Accordion/AccordionItem.svelte +150 -0
- package/dist/components/Accordion/AccordionItem.svelte.d.ts +30 -0
- package/dist/components/App/App.story.md +8 -0
- package/dist/components/App/App.story.svelte +170 -0
- package/dist/components/App/App.story.svelte.d.ts +22 -0
- package/dist/components/App/App.svelte +77 -0
- package/dist/components/App/App.svelte.d.ts +66 -0
- package/dist/components/App/Split.svelte +346 -0
- package/dist/components/App/Split.svelte.d.ts +54 -0
- package/dist/components/App/index.d.ts +2 -0
- package/dist/components/App/index.js +3 -0
- package/dist/components/AppHeader/AppHeader.svelte +439 -0
- package/dist/components/AppHeader/AppHeader.svelte.d.ts +24 -0
- package/dist/components/Avatar/Avatar.svelte +300 -0
- package/dist/components/Avatar/Avatar.svelte.d.ts +48 -0
- package/dist/components/Avatar/AvatarGroup.svelte +185 -0
- package/dist/components/Avatar/AvatarGroup.svelte.d.ts +46 -0
- package/dist/components/Badge/Badge.svelte +186 -0
- package/dist/components/Badge/Badge.svelte.d.ts +51 -0
- package/dist/components/BottomBar/BottomBar.svelte +146 -0
- package/dist/components/BottomBar/BottomBar.svelte.d.ts +38 -0
- package/dist/components/Breadcrumb/Breadcrumb.svelte +77 -0
- package/dist/components/Breadcrumb/Breadcrumb.svelte.d.ts +42 -0
- package/dist/components/Breadcrumb/BreadcrumbItem.svelte +171 -0
- package/dist/components/Breadcrumb/BreadcrumbItem.svelte.d.ts +38 -0
- package/dist/components/Button/Button.svelte +252 -0
- package/dist/components/Button/Button.svelte.d.ts +80 -0
- package/dist/components/Button/ButtonGroup.svelte +127 -0
- package/dist/components/Button/ButtonGroup.svelte.d.ts +44 -0
- package/dist/components/Card/Card.svelte +152 -0
- package/dist/components/Card/Card.svelte.d.ts +55 -0
- package/dist/components/Carousel/Carousel.svelte +461 -0
- package/dist/components/Carousel/Carousel.svelte.d.ts +79 -0
- package/dist/components/Carousel/CarouselItem.svelte +149 -0
- package/dist/components/Carousel/CarouselItem.svelte.d.ts +35 -0
- package/dist/components/Chip/Chip.svelte +288 -0
- package/dist/components/Chip/Chip.svelte.d.ts +71 -0
- package/dist/components/Chip/ChipGroup.svelte +190 -0
- package/dist/components/Chip/ChipGroup.svelte.d.ts +71 -0
- package/dist/components/CodeBlock/CodeBlock.svelte +356 -0
- package/dist/components/CodeBlock/CodeBlock.svelte.d.ts +44 -0
- package/dist/components/CodeBlock/index.d.ts +1 -0
- package/dist/components/CodeBlock/index.js +1 -0
- package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte +145 -0
- package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte.d.ts +44 -0
- package/dist/components/CodeEditor/CodeEditor.svelte +229 -0
- package/dist/components/CodeEditor/CodeEditor.svelte.d.ts +23 -0
- package/dist/components/Combobox/Combobox.svelte +279 -0
- package/dist/components/Combobox/Combobox.svelte.d.ts +34 -0
- package/dist/components/Container/Container.svelte +45 -0
- package/dist/components/Container/Container.svelte.d.ts +36 -0
- package/dist/components/DataTable/DataTable.svelte +879 -0
- package/dist/components/DataTable/DataTable.svelte.d.ts +102 -0
- package/dist/components/Form/AutoComplete.svelte +357 -0
- package/dist/components/Form/AutoComplete.svelte.d.ts +73 -0
- package/dist/components/Form/Calendar.svelte +429 -0
- package/dist/components/Form/Calendar.svelte.d.ts +53 -0
- package/dist/components/Form/Checkbox.svelte +196 -0
- package/dist/components/Form/Checkbox.svelte.d.ts +50 -0
- package/dist/components/Form/ColorPicker.svelte +396 -0
- package/dist/components/Form/ColorPicker.svelte.d.ts +43 -0
- package/dist/components/Form/Combobox.svelte +645 -0
- package/dist/components/Form/Combobox.svelte.d.ts +93 -0
- package/dist/components/Form/Dropdown.svelte +773 -0
- package/dist/components/Form/Dropdown.svelte.d.ts +81 -0
- package/dist/components/Form/FileUpload.svelte +796 -0
- package/dist/components/Form/FileUpload.svelte.d.ts +78 -0
- package/dist/components/Form/FloatLabel.svelte +245 -0
- package/dist/components/Form/FloatLabel.svelte.d.ts +44 -0
- package/dist/components/Form/Form.svelte +281 -0
- package/dist/components/Form/Form.svelte.d.ts +54 -0
- package/dist/components/Form/FormField.svelte +218 -0
- package/dist/components/Form/FormField.svelte.d.ts +47 -0
- package/dist/components/Form/Input.svelte +340 -0
- package/dist/components/Form/Input.svelte.d.ts +79 -0
- package/dist/components/Form/InputSwitch.svelte +189 -0
- package/dist/components/Form/InputSwitch.svelte.d.ts +46 -0
- package/dist/components/Form/InvalidState.svelte +97 -0
- package/dist/components/Form/InvalidState.svelte.d.ts +37 -0
- package/dist/components/Form/Knob.svelte +537 -0
- package/dist/components/Form/Knob.svelte.d.ts +78 -0
- package/dist/components/Form/ListInput.svelte +469 -0
- package/dist/components/Form/ListInput.svelte.d.ts +70 -0
- package/dist/components/Form/Listbox.svelte +513 -0
- package/dist/components/Form/Listbox.svelte.d.ts +74 -0
- package/dist/components/Form/NumberInput.svelte +452 -0
- package/dist/components/Form/NumberInput.svelte.d.ts +82 -0
- package/dist/components/Form/Radio.svelte +192 -0
- package/dist/components/Form/Radio.svelte.d.ts +53 -0
- package/dist/components/Form/RadioGroup.svelte +155 -0
- package/dist/components/Form/RadioGroup.svelte.d.ts +48 -0
- package/dist/components/Form/Rating.svelte +380 -0
- package/dist/components/Form/Rating.svelte.d.ts +64 -0
- package/dist/components/Form/Select.svelte +436 -0
- package/dist/components/Form/Select.svelte.d.ts +49 -0
- package/dist/components/Form/SelectGroup.svelte +34 -0
- package/dist/components/Form/SelectGroup.svelte.d.ts +33 -0
- package/dist/components/Form/Slider.svelte +622 -0
- package/dist/components/Form/Slider.svelte.d.ts +73 -0
- package/dist/components/Form/Switch.svelte +192 -0
- package/dist/components/Form/Switch.svelte.d.ts +46 -0
- package/dist/components/Form/TextInput.svelte +274 -0
- package/dist/components/Form/TextInput.svelte.d.ts +74 -0
- package/dist/components/Form/Textarea.svelte +207 -0
- package/dist/components/Form/Textarea.svelte.d.ts +62 -0
- package/dist/components/Icon/Icon.svelte +140 -0
- package/dist/components/Icon/Icon.svelte.d.ts +25 -0
- package/dist/components/Icon/index.d.ts +1 -0
- package/dist/components/Icon/index.js +1 -0
- package/dist/components/Lazy/Lazy.svelte +158 -0
- package/dist/components/Lazy/Lazy.svelte.d.ts +42 -0
- package/dist/components/Masonry/Masonry.svelte +299 -0
- package/dist/components/Masonry/Masonry.svelte.d.ts +55 -0
- package/dist/components/Menu/Menu/Menu.svelte +65 -0
- package/dist/components/Menu/Menu/Menu.svelte.d.ts +17 -0
- package/dist/components/Menu/Menu/MenuItem.svelte +90 -0
- package/dist/components/Menu/Menu/MenuItem.svelte.d.ts +27 -0
- package/dist/components/Modal/Modal.svelte +334 -0
- package/dist/components/Modal/Modal.svelte.d.ts +55 -0
- package/dist/components/Panel/Card.svelte +141 -0
- package/dist/components/Panel/Card.svelte.d.ts +52 -0
- package/dist/components/Panel/Hero/Hero.story.md +9 -0
- package/dist/components/Panel/Hero/Hero.story.svelte +49 -0
- package/dist/components/Panel/Hero/Hero.story.svelte.d.ts +21 -0
- package/dist/components/Panel/Hero/Hero.svelte +24 -0
- package/dist/components/Panel/Hero/Hero.svelte.d.ts +32 -0
- package/dist/components/Panel/LazyPanel.svelte +110 -0
- package/dist/components/Panel/LazyPanel.svelte.d.ts +46 -0
- package/dist/components/Panel/Panel.svelte +205 -0
- package/dist/components/Panel/Panel.svelte.d.ts +23 -0
- package/dist/components/Progress/Progress.svelte +220 -0
- package/dist/components/Progress/Progress.svelte.d.ts +61 -0
- package/dist/components/Separator/Separator.svelte +109 -0
- package/dist/components/Separator/Separator.svelte.d.ts +35 -0
- package/dist/components/Sidebar/Sidebar.svelte +213 -0
- package/dist/components/Sidebar/Sidebar.svelte.d.ts +60 -0
- package/dist/components/Skeleton/Skeleton.svelte +170 -0
- package/dist/components/Skeleton/Skeleton.svelte.d.ts +48 -0
- package/dist/components/Stepper/Stepper.svelte +111 -0
- package/dist/components/Stepper/Stepper.svelte.d.ts +54 -0
- package/dist/components/Stepper/StepperStep.svelte +369 -0
- package/dist/components/Stepper/StepperStep.svelte.d.ts +63 -0
- package/dist/components/Table/Table.svelte +167 -0
- package/dist/components/Table/Table.svelte.d.ts +56 -0
- package/dist/components/Table/TableBody.svelte +41 -0
- package/dist/components/Table/TableBody.svelte.d.ts +33 -0
- package/dist/components/Table/TableCell.svelte +76 -0
- package/dist/components/Table/TableCell.svelte.d.ts +36 -0
- package/dist/components/Table/TableHead.svelte +41 -0
- package/dist/components/Table/TableHead.svelte.d.ts +32 -0
- package/dist/components/Table/TableHeader.svelte +148 -0
- package/dist/components/Table/TableHeader.svelte.d.ts +42 -0
- package/dist/components/Table/TableRow.svelte +99 -0
- package/dist/components/Table/TableRow.svelte.d.ts +40 -0
- package/dist/components/Tabs/Tab.svelte +145 -0
- package/dist/components/Tabs/Tab.svelte.d.ts +36 -0
- package/dist/components/Tabs/TabList.svelte +60 -0
- package/dist/components/Tabs/TabList.svelte.d.ts +32 -0
- package/dist/components/Tabs/TabPanel.svelte +118 -0
- package/dist/components/Tabs/TabPanel.svelte.d.ts +38 -0
- package/dist/components/Tabs/Tabs.svelte +287 -0
- package/dist/components/Tabs/Tabs.svelte.d.ts +50 -0
- package/dist/components/Tag/Tag.svelte +260 -0
- package/dist/components/Tag/Tag.svelte.d.ts +54 -0
- package/dist/components/Tag/TagGroup.svelte +147 -0
- package/dist/components/Tag/TagGroup.svelte.d.ts +62 -0
- package/dist/components/ThemeToggle/ThemeToggle.svelte +93 -0
- package/dist/components/ThemeToggle/ThemeToggle.svelte.d.ts +12 -0
- package/dist/components/Timeline/Timeline.svelte +144 -0
- package/dist/components/Timeline/Timeline.svelte.d.ts +48 -0
- package/dist/components/Timeline/TimelineItem.svelte +391 -0
- package/dist/components/Timeline/TimelineItem.svelte.d.ts +63 -0
- package/dist/components/Toast/Toast.svelte +313 -0
- package/dist/components/Toast/Toast.svelte.d.ts +44 -0
- package/dist/components/Toast/toastStore.d.ts +40 -0
- package/dist/components/Toast/toastStore.js +293 -0
- package/dist/components/Tooltip/Tooltip.svelte +282 -0
- package/dist/components/Tooltip/Tooltip.svelte.d.ts +55 -0
- package/dist/components/Tree/Tree.svelte +129 -0
- package/dist/components/Tree/Tree.svelte.d.ts +61 -0
- package/dist/components/Tree/TreeNode.svelte +332 -0
- package/dist/components/Tree/TreeNode.svelte.d.ts +55 -0
- package/dist/components/icons/TwintrinsicLogo.svelte +73 -0
- package/dist/components/icons/TwintrinsicLogo.svelte.d.ts +17 -0
- package/dist/components/icons/twintrinsic-source.svg +73 -0
- package/dist/components/icons/twintrinsic.svg +38 -0
- package/dist/docs/EventsTable.svelte +86 -0
- package/dist/docs/EventsTable.svelte.d.ts +27 -0
- package/dist/docs/PropsTable.svelte +103 -0
- package/dist/docs/PropsTable.svelte.d.ts +28 -0
- package/dist/docs/index.d.ts +2 -0
- package/dist/docs/index.js +2 -0
- package/dist/helpers/detectLanguage.d.ts +6 -0
- package/dist/helpers/detectLanguage.js +60 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +94 -0
- package/dist/twintrinsic.css +347 -0
- package/package.json +98 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
NumberInput - A specialized input for numeric values with formatting, units, and increment controls.
|
|
4
|
+
Provides consistent styling, accessibility features, and integration with the Form component.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
```svelte
|
|
8
|
+
<NumberInput
|
|
9
|
+
name="quantity"
|
|
10
|
+
value={1}
|
|
11
|
+
min={0}
|
|
12
|
+
max={100}
|
|
13
|
+
step={1}
|
|
14
|
+
/>
|
|
15
|
+
|
|
16
|
+
<NumberInput
|
|
17
|
+
name="price"
|
|
18
|
+
value={29.99}
|
|
19
|
+
prefix="$"
|
|
20
|
+
decimalPlaces={2}
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<NumberInput
|
|
24
|
+
name="percentage"
|
|
25
|
+
value={75}
|
|
26
|
+
suffix="%"
|
|
27
|
+
verticalButtons
|
|
28
|
+
/>
|
|
29
|
+
```
|
|
30
|
+
-->
|
|
31
|
+
<script>
|
|
32
|
+
import { getContext } from "svelte"
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
/** @type {string} - Additional CSS classes */
|
|
36
|
+
class: className = "",
|
|
37
|
+
|
|
38
|
+
/** @type {string} - HTML id for accessibility */
|
|
39
|
+
id = crypto.randomUUID(),
|
|
40
|
+
|
|
41
|
+
/** @type {string} - Input name */
|
|
42
|
+
name,
|
|
43
|
+
|
|
44
|
+
/** @type {number} - Input value */
|
|
45
|
+
value = 0,
|
|
46
|
+
|
|
47
|
+
/** @type {string} - Placeholder text */
|
|
48
|
+
placeholder = "",
|
|
49
|
+
|
|
50
|
+
/** @type {number} - Minimum allowed value */
|
|
51
|
+
min,
|
|
52
|
+
|
|
53
|
+
/** @type {number} - Maximum allowed value */
|
|
54
|
+
max,
|
|
55
|
+
|
|
56
|
+
/** @type {number} - Step increment/decrement amount */
|
|
57
|
+
step = 1,
|
|
58
|
+
|
|
59
|
+
/** @type {number} - Number of decimal places to display */
|
|
60
|
+
decimalPlaces,
|
|
61
|
+
|
|
62
|
+
/** @type {string} - Text to display before the number */
|
|
63
|
+
prefix,
|
|
64
|
+
|
|
65
|
+
/** @type {string} - Text to display after the number */
|
|
66
|
+
suffix,
|
|
67
|
+
|
|
68
|
+
/** @type {boolean} - Whether to show increment/decrement buttons */
|
|
69
|
+
showButtons = true,
|
|
70
|
+
|
|
71
|
+
/** @type {boolean} - Whether to arrange buttons vertically */
|
|
72
|
+
verticalButtons = false,
|
|
73
|
+
|
|
74
|
+
/** @type {boolean} - Whether the input is required */
|
|
75
|
+
required = false,
|
|
76
|
+
|
|
77
|
+
/** @type {boolean} - Whether the input is disabled */
|
|
78
|
+
disabled = false,
|
|
79
|
+
|
|
80
|
+
/** @type {boolean} - Whether the input is readonly */
|
|
81
|
+
readonly = false,
|
|
82
|
+
|
|
83
|
+
/** @type {string} - Size of the input (sm, md, lg) */
|
|
84
|
+
size = "md",
|
|
85
|
+
|
|
86
|
+
/** @type {string} - ARIA label for accessibility */
|
|
87
|
+
ariaLabel,
|
|
88
|
+
|
|
89
|
+
/** @type {(event: CustomEvent) => void} - Change event handler */
|
|
90
|
+
onchange,
|
|
91
|
+
/** @type {(event: CustomEvent) => void} - Input event handler */
|
|
92
|
+
oninput,
|
|
93
|
+
/** @type {(event: Event) => void} - Focus event handler */
|
|
94
|
+
onfocus,
|
|
95
|
+
/** @type {(event: Event) => void} - Blur event handler */
|
|
96
|
+
onblur,
|
|
97
|
+
} = $props()
|
|
98
|
+
|
|
99
|
+
// Get form context if available
|
|
100
|
+
const formContext = getContext("form")
|
|
101
|
+
|
|
102
|
+
// Generate unique ID if not provided
|
|
103
|
+
const inputId = id || `number-input-${crypto.randomUUID()}`
|
|
104
|
+
|
|
105
|
+
// Input state
|
|
106
|
+
let inputValue = $state(formatValue(value))
|
|
107
|
+
let numericValue = $state(value)
|
|
108
|
+
let isFocused = $state(false)
|
|
109
|
+
let inputEl
|
|
110
|
+
|
|
111
|
+
// Register with form if available
|
|
112
|
+
let fieldApi = $state()
|
|
113
|
+
|
|
114
|
+
$effect(() => {
|
|
115
|
+
if (formContext && name) {
|
|
116
|
+
fieldApi = formContext.registerField(name, value)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Update value when form field changes
|
|
121
|
+
$effect(() => {
|
|
122
|
+
if (fieldApi) {
|
|
123
|
+
const formValue = fieldApi.getValue()
|
|
124
|
+
if (formValue !== undefined && formValue !== numericValue) {
|
|
125
|
+
numericValue = formValue
|
|
126
|
+
inputValue = formatValue(formValue)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// Update internal value when prop changes
|
|
132
|
+
$effect(() => {
|
|
133
|
+
if (value !== numericValue) {
|
|
134
|
+
numericValue = value
|
|
135
|
+
inputValue = formatValue(value)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Formats a numeric value for display
|
|
141
|
+
* @param {number} val - Value to format
|
|
142
|
+
* @returns {string} - Formatted value
|
|
143
|
+
*/
|
|
144
|
+
function formatValue(val) {
|
|
145
|
+
if (val === undefined || val === null) return ""
|
|
146
|
+
|
|
147
|
+
let formatted = decimalPlaces !== undefined ? val.toFixed(decimalPlaces) : val.toString()
|
|
148
|
+
|
|
149
|
+
return formatted
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Parses a string value to a number
|
|
154
|
+
* @param {string} val - Value to parse
|
|
155
|
+
* @returns {number} - Parsed number
|
|
156
|
+
*/
|
|
157
|
+
function parseValue(val) {
|
|
158
|
+
if (!val) return 0
|
|
159
|
+
|
|
160
|
+
// Remove non-numeric characters except decimal point
|
|
161
|
+
const numericString = val.replace(/[^\d.-]/g, "")
|
|
162
|
+
return parseFloat(numericString)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Constrains a value to min/max bounds
|
|
167
|
+
* @param {number} val - Value to constrain
|
|
168
|
+
* @returns {number} - Constrained value
|
|
169
|
+
*/
|
|
170
|
+
function constrainValue(val) {
|
|
171
|
+
let constrained = val
|
|
172
|
+
|
|
173
|
+
if (min !== undefined && constrained < min) {
|
|
174
|
+
constrained = min
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (max !== undefined && constrained > max) {
|
|
178
|
+
constrained = max
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return constrained
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Increments the current value
|
|
186
|
+
*/
|
|
187
|
+
function increment() {
|
|
188
|
+
if (disabled || readonly) return
|
|
189
|
+
|
|
190
|
+
const currentValue = parseValue(inputValue)
|
|
191
|
+
const newValue = constrainValue(currentValue + step)
|
|
192
|
+
|
|
193
|
+
numericValue = newValue
|
|
194
|
+
inputValue = formatValue(newValue)
|
|
195
|
+
|
|
196
|
+
// Update form field if available
|
|
197
|
+
if (fieldApi) {
|
|
198
|
+
fieldApi.setValue(newValue)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
onchange?.(new CustomEvent("change", { detail: { value: newValue } }))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Decrements the current value
|
|
206
|
+
*/
|
|
207
|
+
function decrement() {
|
|
208
|
+
if (disabled || readonly) return
|
|
209
|
+
|
|
210
|
+
const currentValue = parseValue(inputValue)
|
|
211
|
+
const newValue = constrainValue(currentValue - step)
|
|
212
|
+
|
|
213
|
+
numericValue = newValue
|
|
214
|
+
inputValue = formatValue(newValue)
|
|
215
|
+
|
|
216
|
+
// Update form field if available
|
|
217
|
+
if (fieldApi) {
|
|
218
|
+
fieldApi.setValue(newValue)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
onchange?.(new CustomEvent("change", { detail: { value: newValue } }))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Handles input change
|
|
226
|
+
* @param {Event} event - Input event
|
|
227
|
+
*/
|
|
228
|
+
function handleInput(event) {
|
|
229
|
+
const rawValue = event.target.value
|
|
230
|
+
inputValue = rawValue
|
|
231
|
+
|
|
232
|
+
// Only update numeric value if it's a valid number
|
|
233
|
+
const parsed = parseValue(rawValue)
|
|
234
|
+
if (!isNaN(parsed)) {
|
|
235
|
+
numericValue = parsed
|
|
236
|
+
|
|
237
|
+
// Update form field if available
|
|
238
|
+
if (fieldApi) {
|
|
239
|
+
fieldApi.setValue(parsed)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
oninput?.(new CustomEvent("input", { detail: { value: parsed } }))
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Handles blur event
|
|
248
|
+
*/
|
|
249
|
+
function handleBlur(event) {
|
|
250
|
+
// Format and constrain the value on blur
|
|
251
|
+
const parsed = parseValue(inputValue)
|
|
252
|
+
const constrained = constrainValue(parsed)
|
|
253
|
+
|
|
254
|
+
numericValue = constrained
|
|
255
|
+
inputValue = formatValue(constrained)
|
|
256
|
+
|
|
257
|
+
// Update form field if available
|
|
258
|
+
if (fieldApi) {
|
|
259
|
+
fieldApi.setValue(constrained)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
isFocused = false
|
|
263
|
+
onblur?.(event)
|
|
264
|
+
onchange?.(new CustomEvent("change", { detail: { value: constrained } }))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Handles focus event
|
|
269
|
+
*/
|
|
270
|
+
function handleFocus(event) {
|
|
271
|
+
isFocused = true
|
|
272
|
+
onfocus?.(event)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Handles keydown event
|
|
277
|
+
* @param {KeyboardEvent} event - Keydown event
|
|
278
|
+
*/
|
|
279
|
+
function handleKeydown(event) {
|
|
280
|
+
if (disabled || readonly) return
|
|
281
|
+
|
|
282
|
+
if (event.key === "ArrowUp") {
|
|
283
|
+
event.preventDefault()
|
|
284
|
+
increment()
|
|
285
|
+
} else if (event.key === "ArrowDown") {
|
|
286
|
+
event.preventDefault()
|
|
287
|
+
decrement()
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Determine size classes
|
|
292
|
+
const sizeClasses = $derived(
|
|
293
|
+
{
|
|
294
|
+
sm: "h-8 text-sm",
|
|
295
|
+
md: "h-10 text-base",
|
|
296
|
+
lg: "h-12 text-lg",
|
|
297
|
+
}[size] || "h-10 text-base"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
// Determine button size classes
|
|
301
|
+
const buttonSizeClasses = $derived(
|
|
302
|
+
{
|
|
303
|
+
sm: "w-6 h-6",
|
|
304
|
+
md: "w-8 h-8",
|
|
305
|
+
lg: "w-10 h-10",
|
|
306
|
+
}[size] || "w-8 h-8"
|
|
307
|
+
)
|
|
308
|
+
</script>
|
|
309
|
+
|
|
310
|
+
<div
|
|
311
|
+
class="
|
|
312
|
+
number-input
|
|
313
|
+
{verticalButtons ? 'number-input-vertical' : 'number-input-horizontal'}
|
|
314
|
+
{className}
|
|
315
|
+
"
|
|
316
|
+
>
|
|
317
|
+
<div class="number-input-container {sizeClasses}">
|
|
318
|
+
{#if prefix}
|
|
319
|
+
<span class="number-input-prefix">{prefix}</span>
|
|
320
|
+
{/if}
|
|
321
|
+
|
|
322
|
+
<input
|
|
323
|
+
{id}
|
|
324
|
+
{name}
|
|
325
|
+
type="text"
|
|
326
|
+
inputmode="decimal"
|
|
327
|
+
class="number-input-field"
|
|
328
|
+
{placeholder}
|
|
329
|
+
value={inputValue}
|
|
330
|
+
{required}
|
|
331
|
+
{disabled}
|
|
332
|
+
{readonly}
|
|
333
|
+
min={min !== undefined ? min : undefined}
|
|
334
|
+
max={max !== undefined ? max : undefined}
|
|
335
|
+
step={step}
|
|
336
|
+
aria-label={ariaLabel || name}
|
|
337
|
+
aria-valuemin={min !== undefined ? min : undefined}
|
|
338
|
+
aria-valuemax={max !== undefined ? max : undefined}
|
|
339
|
+
aria-valuenow={numericValue}
|
|
340
|
+
bind:this={inputEl}
|
|
341
|
+
oninput={handleInput}
|
|
342
|
+
onblur={handleBlur}
|
|
343
|
+
onfocus={handleFocus}
|
|
344
|
+
onkeydown={handleKeydown}
|
|
345
|
+
/>
|
|
346
|
+
|
|
347
|
+
{#if suffix}
|
|
348
|
+
<span class="number-input-suffix">{suffix}</span>
|
|
349
|
+
{/if}
|
|
350
|
+
|
|
351
|
+
{#if showButtons}
|
|
352
|
+
<div class="number-input-buttons {verticalButtons ? 'number-input-buttons-vertical' : ''}">
|
|
353
|
+
<button
|
|
354
|
+
type="button"
|
|
355
|
+
class="number-input-button number-input-increment {buttonSizeClasses}"
|
|
356
|
+
aria-label="Increase value"
|
|
357
|
+
tabindex="-1"
|
|
358
|
+
disabled={disabled || readonly || (max !== undefined && numericValue >= max)}
|
|
359
|
+
onclick={increment}
|
|
360
|
+
>
|
|
361
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
362
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
|
|
363
|
+
</svg>
|
|
364
|
+
</button>
|
|
365
|
+
|
|
366
|
+
<button
|
|
367
|
+
type="button"
|
|
368
|
+
class="number-input-button number-input-decrement {buttonSizeClasses}"
|
|
369
|
+
aria-label="Decrease value"
|
|
370
|
+
tabindex="-1"
|
|
371
|
+
disabled={disabled || readonly || (min !== undefined && numericValue <= min)}
|
|
372
|
+
onclick={decrement}
|
|
373
|
+
>
|
|
374
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
375
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
376
|
+
</svg>
|
|
377
|
+
</button>
|
|
378
|
+
</div>
|
|
379
|
+
{/if}
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<style>
|
|
384
|
+
@reference "../../twintrinsic.css";
|
|
385
|
+
|
|
386
|
+
.number-input {
|
|
387
|
+
@apply w-full;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.number-input-container {
|
|
391
|
+
@apply relative flex items-center;
|
|
392
|
+
@apply bg-background dark:bg-background;
|
|
393
|
+
@apply border border-border dark:border-border rounded-md;
|
|
394
|
+
@apply focus-within:ring-2 focus-within:ring-primary-500 dark:focus-within:ring-primary-400 focus-within:border-primary-500 dark:focus-within:border-primary-400;
|
|
395
|
+
@apply transition-colors duration-200;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.number-input-field {
|
|
399
|
+
@apply w-full h-full px-3 bg-transparent border-none outline-none;
|
|
400
|
+
@apply text-text dark:text-text placeholder:text-muted dark:placeholder:text-muted;
|
|
401
|
+
@apply disabled:opacity-50 disabled:cursor-not-allowed;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.number-input-prefix {
|
|
405
|
+
@apply pl-3 text-muted dark:text-muted;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.number-input-suffix {
|
|
409
|
+
@apply pr-3 text-muted dark:text-muted;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.number-input-buttons {
|
|
413
|
+
@apply flex;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.number-input-buttons-vertical {
|
|
417
|
+
@apply flex-col absolute right-0 top-0 h-full;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.number-input-horizontal .number-input-field {
|
|
421
|
+
@apply pr-16;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.number-input-horizontal .number-input-buttons {
|
|
425
|
+
@apply absolute right-0 h-full border-l border-border dark:border-border;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.number-input-button {
|
|
429
|
+
@apply flex items-center justify-center;
|
|
430
|
+
@apply text-muted dark:text-muted;
|
|
431
|
+
@apply hover:bg-hover dark:hover:bg-hover hover:text-text dark:hover:text-text;
|
|
432
|
+
@apply focus:outline-none focus:bg-hover dark:focus:bg-hover;
|
|
433
|
+
@apply disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent dark:disabled:hover:bg-transparent;
|
|
434
|
+
@apply transition-colors duration-150;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.number-input-vertical .number-input-increment {
|
|
438
|
+
@apply rounded-tr-md border-b border-border dark:border-border;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.number-input-vertical .number-input-decrement {
|
|
442
|
+
@apply rounded-br-md;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.number-input-horizontal .number-input-increment {
|
|
446
|
+
@apply border-r border-border dark:border-border;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.number-input-horizontal .number-input-decrement {
|
|
450
|
+
@apply rounded-r-md;
|
|
451
|
+
}
|
|
452
|
+
</style>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export default NumberInput;
|
|
2
|
+
type NumberInput = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* NumberInput - A specialized input for numeric values with formatting, units, and increment controls.
|
|
8
|
+
* Provides consistent styling, accessibility features, and integration with the Form component.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <NumberInput
|
|
13
|
+
* name="quantity"
|
|
14
|
+
* value={1}
|
|
15
|
+
* min={0}
|
|
16
|
+
* max={100}
|
|
17
|
+
* step={1}
|
|
18
|
+
* />
|
|
19
|
+
*
|
|
20
|
+
* <NumberInput
|
|
21
|
+
* name="price"
|
|
22
|
+
* value={29.99}
|
|
23
|
+
* prefix="$"
|
|
24
|
+
* decimalPlaces={2}
|
|
25
|
+
* />
|
|
26
|
+
*
|
|
27
|
+
* <NumberInput
|
|
28
|
+
* name="percentage"
|
|
29
|
+
* value={75}
|
|
30
|
+
* suffix="%"
|
|
31
|
+
* verticalButtons
|
|
32
|
+
* />
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare const NumberInput: import("svelte").Component<{
|
|
36
|
+
class?: string;
|
|
37
|
+
id?: any;
|
|
38
|
+
name: any;
|
|
39
|
+
value?: number;
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
min: any;
|
|
42
|
+
max: any;
|
|
43
|
+
step?: number;
|
|
44
|
+
decimalPlaces: any;
|
|
45
|
+
prefix: any;
|
|
46
|
+
suffix: any;
|
|
47
|
+
showButtons?: boolean;
|
|
48
|
+
verticalButtons?: boolean;
|
|
49
|
+
required?: boolean;
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
readonly?: boolean;
|
|
52
|
+
size?: string;
|
|
53
|
+
ariaLabel: any;
|
|
54
|
+
onchange: any;
|
|
55
|
+
oninput: any;
|
|
56
|
+
onfocus: any;
|
|
57
|
+
onblur: any;
|
|
58
|
+
}, {}, "">;
|
|
59
|
+
type $$ComponentProps = {
|
|
60
|
+
class?: string;
|
|
61
|
+
id?: any;
|
|
62
|
+
name: any;
|
|
63
|
+
value?: number;
|
|
64
|
+
placeholder?: string;
|
|
65
|
+
min: any;
|
|
66
|
+
max: any;
|
|
67
|
+
step?: number;
|
|
68
|
+
decimalPlaces: any;
|
|
69
|
+
prefix: any;
|
|
70
|
+
suffix: any;
|
|
71
|
+
showButtons?: boolean;
|
|
72
|
+
verticalButtons?: boolean;
|
|
73
|
+
required?: boolean;
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
readonly?: boolean;
|
|
76
|
+
size?: string;
|
|
77
|
+
ariaLabel: any;
|
|
78
|
+
onchange: any;
|
|
79
|
+
oninput: any;
|
|
80
|
+
onfocus: any;
|
|
81
|
+
onblur: any;
|
|
82
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Radio - A styled radio button component.
|
|
4
|
+
Provides consistent styling, accessibility features, and integration with the Form component.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
```svelte
|
|
8
|
+
<Radio
|
|
9
|
+
name="theme"
|
|
10
|
+
value="light"
|
|
11
|
+
label="Light theme"
|
|
12
|
+
checked={theme === 'light'}
|
|
13
|
+
/>
|
|
14
|
+
|
|
15
|
+
<FormField label="Select theme">
|
|
16
|
+
<div class="flex gap-4">
|
|
17
|
+
<Radio name="theme" value="light" label="Light" />
|
|
18
|
+
<Radio name="theme" value="dark" label="Dark" />
|
|
19
|
+
<Radio name="theme" value="system" label="System" />
|
|
20
|
+
</div>
|
|
21
|
+
</FormField>
|
|
22
|
+
```
|
|
23
|
+
-->
|
|
24
|
+
<script>
|
|
25
|
+
import { getContext } from "svelte"
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
/** @type {string} - Additional CSS classes */
|
|
29
|
+
class: className = "",
|
|
30
|
+
|
|
31
|
+
/** @type {string} - HTML id for accessibility */
|
|
32
|
+
id = crypto.randomUUID(),
|
|
33
|
+
|
|
34
|
+
/** @type {string} - Radio name (for grouping) */
|
|
35
|
+
name,
|
|
36
|
+
|
|
37
|
+
/** @type {string} - Radio value */
|
|
38
|
+
value,
|
|
39
|
+
|
|
40
|
+
/** @type {string} - Label text */
|
|
41
|
+
label,
|
|
42
|
+
|
|
43
|
+
/** @type {boolean} - Whether the radio is checked */
|
|
44
|
+
checked = false,
|
|
45
|
+
|
|
46
|
+
/** @type {boolean} - Whether the radio is required */
|
|
47
|
+
required = false,
|
|
48
|
+
|
|
49
|
+
/** @type {boolean} - Whether the radio is disabled */
|
|
50
|
+
disabled = false,
|
|
51
|
+
|
|
52
|
+
/** @type {string} - Size of the radio (sm, md, lg) */
|
|
53
|
+
size = "md",
|
|
54
|
+
|
|
55
|
+
/** @type {string} - ARIA label for accessibility */
|
|
56
|
+
ariaLabel,
|
|
57
|
+
/** @type {(event: CustomEvent) => void} - Change event handler */
|
|
58
|
+
onchange,
|
|
59
|
+
...restProps
|
|
60
|
+
} = $props()
|
|
61
|
+
|
|
62
|
+
// Get form context if available
|
|
63
|
+
const formContext = getContext("form")
|
|
64
|
+
|
|
65
|
+
// Radio state
|
|
66
|
+
let isChecked = $state(checked)
|
|
67
|
+
|
|
68
|
+
// Update checked state when prop changes
|
|
69
|
+
$effect(() => {
|
|
70
|
+
isChecked = checked
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Register with form if available
|
|
74
|
+
let fieldApi = $state()
|
|
75
|
+
|
|
76
|
+
$effect(() => {
|
|
77
|
+
if (formContext && name) {
|
|
78
|
+
fieldApi = formContext.registerField(name, checked ? value : undefined)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Update checked state when form field changes
|
|
83
|
+
$effect(() => {
|
|
84
|
+
if (fieldApi) {
|
|
85
|
+
const formValue = fieldApi.getValue()
|
|
86
|
+
isChecked = formValue === value
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Handles radio change
|
|
92
|
+
* @param {Event} event - Change event
|
|
93
|
+
*/
|
|
94
|
+
function handleChange(event) {
|
|
95
|
+
isChecked = event.target.checked
|
|
96
|
+
|
|
97
|
+
// Update form field if available
|
|
98
|
+
if (fieldApi && isChecked) {
|
|
99
|
+
fieldApi.setValue(value)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
onchange?.(new CustomEvent("change", { detail: { checked: isChecked, value } }))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Determine radio size classes
|
|
106
|
+
const radioSizeClasses = $derived(
|
|
107
|
+
{
|
|
108
|
+
sm: "w-3.5 h-3.5",
|
|
109
|
+
md: "w-4 h-4",
|
|
110
|
+
lg: "w-5 h-5",
|
|
111
|
+
}[size] || "w-4 h-4"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
const labelSizeClasses = $derived(
|
|
115
|
+
{
|
|
116
|
+
sm: "text-xs",
|
|
117
|
+
md: "text-sm",
|
|
118
|
+
lg: "text-base",
|
|
119
|
+
}[size] || "text-sm"
|
|
120
|
+
)
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<label
|
|
124
|
+
class="radio-wrapper {className} {labelSizeClasses} {disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
|
|
125
|
+
>
|
|
126
|
+
<div class="radio-container">
|
|
127
|
+
<input
|
|
128
|
+
type="radio"
|
|
129
|
+
{id}
|
|
130
|
+
{name}
|
|
131
|
+
{value}
|
|
132
|
+
checked={isChecked}
|
|
133
|
+
{required}
|
|
134
|
+
disabled={disabled || (fieldApi && fieldApi.isDisabled())}
|
|
135
|
+
aria-label={ariaLabel || label}
|
|
136
|
+
class="radio-input"
|
|
137
|
+
onchange={handleChange}
|
|
138
|
+
{...restProps}
|
|
139
|
+
/>
|
|
140
|
+
|
|
141
|
+
<span class="radio-control {radioSizeClasses}" aria-hidden="true">
|
|
142
|
+
<span class="radio-dot"></span>
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{#if label}
|
|
147
|
+
<span class="radio-label">{label}</span>
|
|
148
|
+
{/if}
|
|
149
|
+
</label>
|
|
150
|
+
|
|
151
|
+
<style>
|
|
152
|
+
@reference "../../twintrinsic.css";
|
|
153
|
+
|
|
154
|
+
.radio-wrapper {
|
|
155
|
+
@apply inline-flex items-center gap-2;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.radio-container {
|
|
159
|
+
@apply relative flex items-center justify-center;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.radio-input {
|
|
163
|
+
@apply sr-only;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.radio-control {
|
|
167
|
+
@apply rounded-full border-2 border-border dark:border-border bg-background dark:bg-background;
|
|
168
|
+
@apply flex items-center justify-center;
|
|
169
|
+
@apply transition-colors duration-200;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.radio-input:checked + .radio-control {
|
|
173
|
+
@apply border-primary-500 dark:border-primary-400;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.radio-input:focus + .radio-control {
|
|
177
|
+
@apply ring-2 ring-offset-2 ring-primary-500 dark:ring-primary-400;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.radio-dot {
|
|
181
|
+
@apply rounded-full bg-primary-500 dark:bg-primary-400;
|
|
182
|
+
@apply w-0 h-0 opacity-0 transition-all duration-200;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.radio-input:checked + .radio-control .radio-dot {
|
|
186
|
+
@apply w-1/2 h-1/2 opacity-100;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.radio-label {
|
|
190
|
+
@apply text-text dark:text-text;
|
|
191
|
+
}
|
|
192
|
+
</style>
|