@forthtilliath/forth-ui 0.4.0 → 0.4.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/dist/components/color-picker/color-picker.d.ts.map +1 -1
- package/dist/components/color-picker/color-picker.js +7 -1
- package/dist/components/number-input/number-input.d.ts +5 -1
- package/dist/components/number-input/number-input.d.ts.map +1 -1
- package/dist/components/number-input/number-input.js +47 -7
- package/dist/globals.css +70 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"color-picker.d.ts","sourceRoot":"","sources":["../../../src/components/color-picker/color-picker.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,wBAAwB,CAAC;AAEtE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,YAAwB,EACxB,aAAa,EACb,QAAgB,EAChB,SAAS,EACT,SAAS,EACT,MAAM,GACP,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"color-picker.d.ts","sourceRoot":"","sources":["../../../src/components/color-picker/color-picker.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,wBAAwB,CAAC;AAEtE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,YAAwB,EACxB,aAAa,EACb,QAAgB,EAChB,SAAS,EACT,SAAS,EACT,MAAM,GACP,EAAE,gBAAgB,qBA6DlB"}
|
|
@@ -22,8 +22,14 @@ const HEX_PATTERN = /^#[0-9a-f]{6}$/i;
|
|
|
22
22
|
export function ColorPicker({ value, defaultValue = "#000000", onValueChange, disabled = false, className, ariaLabel, locale, }) {
|
|
23
23
|
const messages = useUiMessages(locale);
|
|
24
24
|
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
|
|
25
|
-
const [textValue, setTextValue] = React.useState(defaultValue);
|
|
26
25
|
const color = value ?? uncontrolledValue;
|
|
26
|
+
// Hex text being typed; resynced when the color changes from outside.
|
|
27
|
+
const [textValue, setTextValue] = React.useState(color);
|
|
28
|
+
const [shownColor, setShownColor] = React.useState(color);
|
|
29
|
+
if (color !== shownColor) {
|
|
30
|
+
setShownColor(color);
|
|
31
|
+
setTextValue(color);
|
|
32
|
+
}
|
|
27
33
|
function commit(next) {
|
|
28
34
|
setUncontrolledValue(next);
|
|
29
35
|
setTextValue(next);
|
|
@@ -17,6 +17,10 @@ export type NumberInputProps = Omit<React.ComponentProps<"input">, "type" | "val
|
|
|
17
17
|
* A numeric input with increment/decrement stepper buttons, clamped to
|
|
18
18
|
* `min`/`max`.
|
|
19
19
|
*
|
|
20
|
+
* Typing stays free: in-range values are reported as you type, and the value
|
|
21
|
+
* is only clamped when the field loses focus or on Enter — so `600` can be
|
|
22
|
+
* typed with `min={128}` without the first `6` jumping to `128`.
|
|
23
|
+
*
|
|
20
24
|
* _Inspired from multiple sources, to make a consistent and reusable component._
|
|
21
25
|
* @see https://www.heroui.com/docs/components/number-input
|
|
22
26
|
*
|
|
@@ -24,5 +28,5 @@ export type NumberInputProps = Omit<React.ComponentProps<"input">, "type" | "val
|
|
|
24
28
|
* @author Forth
|
|
25
29
|
* @copyright 2026 Forth
|
|
26
30
|
*/
|
|
27
|
-
export declare function NumberInput({ className, value, defaultValue, onValueChange, min, max, step, disabled, locale, ...props }: NumberInputProps): React.JSX.Element;
|
|
31
|
+
export declare function NumberInput({ className, value, defaultValue, onValueChange, min, max, step, disabled, locale, onBlur, onKeyDown, ...props }: NumberInputProps): React.JSX.Element;
|
|
28
32
|
//# sourceMappingURL=number-input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-input.d.ts","sourceRoot":"","sources":["../../../src/components/number-input/number-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,wBAAwB,CAAC;AAGtE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,EAC7B,MAAM,GAAG,OAAO,GAAG,cAAc,GAAG,UAAU,CAC/C,GAAG;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB,CAAC;
|
|
1
|
+
{"version":3,"file":"number-input.d.ts","sourceRoot":"","sources":["../../../src/components/number-input/number-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,wBAAwB,CAAC;AAGtE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,EAC7B,MAAM,GAAG,OAAO,GAAG,cAAc,GAAG,UAAU,CAC/C,GAAG;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB,CAAC;AAcF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,KAAK,EACL,YAAgB,EAChB,aAAa,EACb,GAAe,EACf,GAAc,EACd,IAAQ,EACR,QAAgB,EAChB,MAAM,EACN,MAAM,EACN,SAAS,EACT,GAAG,KAAK,EACT,EAAE,gBAAgB,qBAuFlB"}
|
|
@@ -6,10 +6,24 @@ import { Input } from "@forthtilliath/shadcn-ui/components/input";
|
|
|
6
6
|
import { cn } from "@forthtilliath/shadcn-ui/lib/utils";
|
|
7
7
|
import { useUiMessages } from "../../locale/locale.js";
|
|
8
8
|
import { Button } from "../button/index.js";
|
|
9
|
+
/** Number typed by the user, accepting a comma as decimal separator. */
|
|
10
|
+
function parseTyped(text) {
|
|
11
|
+
const normalized = text.trim().replace(",", ".");
|
|
12
|
+
return normalized === "" ? Number.NaN : Number(normalized);
|
|
13
|
+
}
|
|
14
|
+
/** Drops float noise from stepping (0.4 + 0.01) at the step's precision. */
|
|
15
|
+
function roundToStep(value, step) {
|
|
16
|
+
const decimals = String(step).split(".")[1]?.length ?? 0;
|
|
17
|
+
return Number(value.toFixed(decimals));
|
|
18
|
+
}
|
|
9
19
|
/**
|
|
10
20
|
* A numeric input with increment/decrement stepper buttons, clamped to
|
|
11
21
|
* `min`/`max`.
|
|
12
22
|
*
|
|
23
|
+
* Typing stays free: in-range values are reported as you type, and the value
|
|
24
|
+
* is only clamped when the field loses focus or on Enter — so `600` can be
|
|
25
|
+
* typed with `min={128}` without the first `6` jumping to `128`.
|
|
26
|
+
*
|
|
13
27
|
* _Inspired from multiple sources, to make a consistent and reusable component._
|
|
14
28
|
* @see https://www.heroui.com/docs/components/number-input
|
|
15
29
|
*
|
|
@@ -17,22 +31,48 @@ import { Button } from "../button/index.js";
|
|
|
17
31
|
* @author Forth
|
|
18
32
|
* @copyright 2026 Forth
|
|
19
33
|
*/
|
|
20
|
-
export function NumberInput({ className, value, defaultValue = 0, onValueChange, min = -Infinity, max = Infinity, step = 1, disabled = false, locale, ...props }) {
|
|
34
|
+
export function NumberInput({ className, value, defaultValue = 0, onValueChange, min = -Infinity, max = Infinity, step = 1, disabled = false, locale, onBlur, onKeyDown, ...props }) {
|
|
21
35
|
const messages = useUiMessages(locale);
|
|
22
36
|
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
|
|
23
37
|
const current = value ?? uncontrolledValue;
|
|
38
|
+
// Raw text while typing; resynced when the value changes from outside.
|
|
39
|
+
const [text, setText] = React.useState(String(current));
|
|
40
|
+
const [shownValue, setShownValue] = React.useState(current);
|
|
41
|
+
if (current !== shownValue) {
|
|
42
|
+
setShownValue(current);
|
|
43
|
+
if (parseTyped(text) !== current)
|
|
44
|
+
setText(String(current));
|
|
45
|
+
}
|
|
24
46
|
function commit(next) {
|
|
25
|
-
const clamped = Math.min(max, Math.max(min, next));
|
|
47
|
+
const clamped = roundToStep(Math.min(max, Math.max(min, next)), step);
|
|
26
48
|
setUncontrolledValue(clamped);
|
|
27
|
-
|
|
49
|
+
setText(String(clamped));
|
|
50
|
+
if (clamped !== current)
|
|
51
|
+
onValueChange?.(clamped);
|
|
52
|
+
}
|
|
53
|
+
function commitText() {
|
|
54
|
+
const parsed = parseTyped(text);
|
|
55
|
+
if (Number.isFinite(parsed))
|
|
56
|
+
commit(parsed);
|
|
57
|
+
else
|
|
58
|
+
setText(String(current));
|
|
28
59
|
}
|
|
29
60
|
return (_jsxs("div", { "data-slot": "number-input", className: cn("flex w-fit items-stretch", className), children: [_jsx(Button, { type: "button", variant: "outline", size: "icon", disabled: disabled || current <= min, onClick: () => {
|
|
30
61
|
commit(current - step);
|
|
31
|
-
}, "aria-label": messages.numberInput.decrement, className: "rounded-r-none", children: _jsx(MinusIcon, {}) }), _jsx(Input, { type: "number", inputMode: "numeric", value:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
}, "aria-label": messages.numberInput.decrement, className: "rounded-r-none", children: _jsx(MinusIcon, {}) }), _jsx(Input, { type: "number", inputMode: Number.isInteger(step) ? "numeric" : "decimal", value: text, min: min === -Infinity ? undefined : min, max: max === Infinity ? undefined : max, step: step, disabled: disabled, onChange: (e) => {
|
|
63
|
+
setText(e.target.value);
|
|
64
|
+
const parsed = parseTyped(e.target.value);
|
|
65
|
+
if (Number.isFinite(parsed) && parsed >= min && parsed <= max) {
|
|
66
|
+
setUncontrolledValue(parsed);
|
|
67
|
+
onValueChange?.(parsed);
|
|
35
68
|
}
|
|
69
|
+
}, onBlur: (e) => {
|
|
70
|
+
commitText();
|
|
71
|
+
onBlur?.(e);
|
|
72
|
+
}, onKeyDown: (e) => {
|
|
73
|
+
if (e.key === "Enter")
|
|
74
|
+
commitText();
|
|
75
|
+
onKeyDown?.(e);
|
|
36
76
|
}, className: "w-16 rounded-none text-center [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none", ...props }), _jsx(Button, { type: "button", variant: "outline", size: "icon", disabled: disabled || current >= max, onClick: () => {
|
|
37
77
|
commit(current + step);
|
|
38
78
|
}, "aria-label": messages.numberInput.increment, className: "rounded-l-none", children: _jsx(PlusIcon, {}) })] }));
|
package/dist/globals.css
CHANGED
|
@@ -2123,6 +2123,10 @@
|
|
|
2123
2123
|
outline-style: var(--tw-outline-style);
|
|
2124
2124
|
outline-width: 1px;
|
|
2125
2125
|
}
|
|
2126
|
+
.blur {
|
|
2127
|
+
--tw-blur: blur(8px);
|
|
2128
|
+
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
|
2129
|
+
}
|
|
2126
2130
|
.backdrop-blur-sm {
|
|
2127
2131
|
--tw-backdrop-blur: blur(var(--blur-sm));
|
|
2128
2132
|
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
|
@@ -4158,6 +4162,59 @@
|
|
|
4158
4162
|
inherits: false;
|
|
4159
4163
|
initial-value: solid;
|
|
4160
4164
|
}
|
|
4165
|
+
@property --tw-blur {
|
|
4166
|
+
syntax: "*";
|
|
4167
|
+
inherits: false;
|
|
4168
|
+
}
|
|
4169
|
+
@property --tw-brightness {
|
|
4170
|
+
syntax: "*";
|
|
4171
|
+
inherits: false;
|
|
4172
|
+
}
|
|
4173
|
+
@property --tw-contrast {
|
|
4174
|
+
syntax: "*";
|
|
4175
|
+
inherits: false;
|
|
4176
|
+
}
|
|
4177
|
+
@property --tw-grayscale {
|
|
4178
|
+
syntax: "*";
|
|
4179
|
+
inherits: false;
|
|
4180
|
+
}
|
|
4181
|
+
@property --tw-hue-rotate {
|
|
4182
|
+
syntax: "*";
|
|
4183
|
+
inherits: false;
|
|
4184
|
+
}
|
|
4185
|
+
@property --tw-invert {
|
|
4186
|
+
syntax: "*";
|
|
4187
|
+
inherits: false;
|
|
4188
|
+
}
|
|
4189
|
+
@property --tw-opacity {
|
|
4190
|
+
syntax: "*";
|
|
4191
|
+
inherits: false;
|
|
4192
|
+
}
|
|
4193
|
+
@property --tw-saturate {
|
|
4194
|
+
syntax: "*";
|
|
4195
|
+
inherits: false;
|
|
4196
|
+
}
|
|
4197
|
+
@property --tw-sepia {
|
|
4198
|
+
syntax: "*";
|
|
4199
|
+
inherits: false;
|
|
4200
|
+
}
|
|
4201
|
+
@property --tw-drop-shadow {
|
|
4202
|
+
syntax: "*";
|
|
4203
|
+
inherits: false;
|
|
4204
|
+
}
|
|
4205
|
+
@property --tw-drop-shadow-color {
|
|
4206
|
+
syntax: "*";
|
|
4207
|
+
inherits: false;
|
|
4208
|
+
}
|
|
4209
|
+
@property --tw-drop-shadow-alpha {
|
|
4210
|
+
syntax: "<percentage>";
|
|
4211
|
+
inherits: false;
|
|
4212
|
+
initial-value: 100%;
|
|
4213
|
+
}
|
|
4214
|
+
@property --tw-drop-shadow-size {
|
|
4215
|
+
syntax: "*";
|
|
4216
|
+
inherits: false;
|
|
4217
|
+
}
|
|
4161
4218
|
@property --tw-backdrop-blur {
|
|
4162
4219
|
syntax: "*";
|
|
4163
4220
|
inherits: false;
|
|
@@ -4355,6 +4412,19 @@
|
|
|
4355
4412
|
--tw-ring-offset-color: #fff;
|
|
4356
4413
|
--tw-ring-offset-shadow: 0 0 #0000;
|
|
4357
4414
|
--tw-outline-style: solid;
|
|
4415
|
+
--tw-blur: initial;
|
|
4416
|
+
--tw-brightness: initial;
|
|
4417
|
+
--tw-contrast: initial;
|
|
4418
|
+
--tw-grayscale: initial;
|
|
4419
|
+
--tw-hue-rotate: initial;
|
|
4420
|
+
--tw-invert: initial;
|
|
4421
|
+
--tw-opacity: initial;
|
|
4422
|
+
--tw-saturate: initial;
|
|
4423
|
+
--tw-sepia: initial;
|
|
4424
|
+
--tw-drop-shadow: initial;
|
|
4425
|
+
--tw-drop-shadow-color: initial;
|
|
4426
|
+
--tw-drop-shadow-alpha: 100%;
|
|
4427
|
+
--tw-drop-shadow-size: initial;
|
|
4358
4428
|
--tw-backdrop-blur: initial;
|
|
4359
4429
|
--tw-backdrop-brightness: initial;
|
|
4360
4430
|
--tw-backdrop-contrast: initial;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forthtilliath/forth-ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"*.css"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"lucide-react": "^1.31.0",
|
|
29
29
|
"qrcode": "^1.5.4",
|
|
30
30
|
"shiki": "^4.4.3",
|
|
31
|
-
"@forthtilliath/react-kit": "^0.
|
|
31
|
+
"@forthtilliath/react-kit": "^0.6.0",
|
|
32
32
|
"@forthtilliath/shadcn-ui": "^0.3.0",
|
|
33
33
|
"@forthtilliath/ts-types": "^0.3.0"
|
|
34
34
|
},
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"size-limit": "^14.1.0",
|
|
60
60
|
"tailwindcss": "4.3.3",
|
|
61
61
|
"typescript": "^5.9.3",
|
|
62
|
-
"@forthtilliath/
|
|
63
|
-
"@forthtilliath/
|
|
62
|
+
"@forthtilliath/eslint-config": "0.9.0",
|
|
63
|
+
"@forthtilliath/typescript-config": "0.1.1"
|
|
64
64
|
},
|
|
65
65
|
"files": [
|
|
66
66
|
"dist"
|