@pallasoft/ps-input-components 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ import { type PsTextInputProps } from './ps-text-input';
2
+ export type PsAmountInputProps = Omit<PsTextInputProps, 'inputMode' | 'type'>;
3
+ export declare const PsAmountInput: ({ className, onChange, ...props }: PsAmountInputProps) => import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsAmountInput = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_amount_input_service_1 = require("../services/ps-amount-input-service");
6
+ const ps_text_input_1 = require("./ps-text-input");
7
+ const PsAmountInput = ({ className, onChange, ...props }) => ((0, jsx_runtime_1.jsx)(ps_text_input_1.PsTextInput, { ...props, autoComplete: 'off', className: className, inputMode: 'decimal', onChange: (event) => {
8
+ const normalised = ps_amount_input_service_1.PsAmountInputService.normalise(event.target.value);
9
+ if (normalised !== event.target.value)
10
+ event.target.value = normalised;
11
+ if (onChange)
12
+ onChange(event);
13
+ }, type: 'text' }));
14
+ exports.PsAmountInput = PsAmountInput;
@@ -6,27 +6,12 @@ const react_1 = require("react");
6
6
  const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
7
  const ps_icons_1 = require("@pallasoft/ps-icons");
8
8
  const ps_color_input_config_1 = require("../constants/ps-color-input-config");
9
+ const ps_color_picker_1 = require("./ps-color-picker");
9
10
  const PsColorInput = ({ className, helperText, label, onChange, swatches = [...ps_color_input_config_1.PsColorInputConfig.swatches], value, }) => {
10
11
  const labelId = (0, react_1.useId)();
11
12
  const swatchesId = (0, react_1.useId)();
12
- const [draft, setDraft] = (0, react_1.useState)(value);
13
13
  const normalised = value.toUpperCase();
14
- (0, react_1.useEffect)(() => {
15
- setDraft(value);
16
- }, [value]);
17
- const commit = (next) => {
18
- const hex = next.startsWith('#') ? next : `#${next}`;
19
- if (ps_color_input_config_1.PsColorInputConfig.hexPattern.test(hex))
20
- onChange(hex.toUpperCase());
21
- else
22
- setDraft(value);
23
- };
24
- return ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('flex flex-col gap-2', className), children: [(0, jsx_runtime_1.jsx)("span", { className: 'ps-input-label', id: labelId, children: label }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-4 rounded-ps-control border border-line bg-surface p-4', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-3', children: [(0, jsx_runtime_1.jsx)("label", { className: 'ps-focus-ring relative h-11 w-11 shrink-0 cursor-pointer overflow-hidden rounded-ps-control border border-line ring-offset-2 ring-offset-surface', style: { backgroundColor: value === '' ? 'transparent' : value }, title: 'Pick a colour', children: (0, jsx_runtime_1.jsx)("input", { "aria-label": `${label} picker`, className: 'absolute inset-0 cursor-pointer opacity-0', onChange: (event) => onChange(event.target.value.toUpperCase()), type: 'color', value: value === '' ? '#000000' : value }) }), (0, jsx_runtime_1.jsx)("input", { "aria-label": `${label} hex`, className: 'ps-input-control ps-focus-ring h-11 min-w-0 flex-1 font-mono text-sm uppercase', maxLength: 7, onBlur: (event) => commit(event.target.value), onChange: (event) => setDraft(event.target.value), onKeyDown: (event) => {
25
- if (event.key === 'Enter') {
26
- event.preventDefault();
27
- commit(draft);
28
- }
29
- }, placeholder: '#000000', value: draft })] }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-2 border-t border-line pt-4', children: [(0, jsx_runtime_1.jsx)("span", { className: 'ps-input-helper', id: swatchesId, children: "Presets" }), (0, jsx_runtime_1.jsx)("div", { "aria-labelledby": swatchesId, className: 'flex flex-wrap gap-2', role: 'radiogroup', children: swatches.map((swatch) => {
14
+ return ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('flex flex-col gap-2', className), children: [(0, jsx_runtime_1.jsx)("span", { className: 'ps-input-label', id: labelId, children: label }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-4 rounded-ps-control border border-line bg-surface p-4', children: [(0, jsx_runtime_1.jsx)(ps_color_picker_1.PsColorPicker, { hideLabel: true, label: `${label} picker`, onChange: onChange, swatches: swatches, value: value }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-2 border-t border-line pt-4', children: [(0, jsx_runtime_1.jsx)("span", { className: 'ps-input-helper', id: swatchesId, children: "Presets" }), (0, jsx_runtime_1.jsx)("div", { "aria-labelledby": swatchesId, className: 'flex flex-wrap gap-2', role: 'radiogroup', children: swatches.map((swatch) => {
30
15
  const isSelected = swatch.toUpperCase() === normalised;
31
16
  return ((0, jsx_runtime_1.jsx)("button", { "aria-checked": isSelected, "aria-label": swatch, className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring flex h-8 w-8 items-center justify-center rounded-full text-white ring-offset-2 ring-offset-surface transition-shadow', isSelected ? 'ring-2 ring-ink/70' : 'hover:ring-2 hover:ring-ink/25'), onClick: () => onChange(swatch.toUpperCase()), role: 'radio', style: { backgroundColor: swatch }, type: 'button', children: isSelected ? ((0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: ps_icons_1.PsIconKey.Check })) : null }, swatch));
32
17
  }) })] })] }), helperText ? (0, jsx_runtime_1.jsx)("span", { className: 'ps-input-helper', children: helperText }) : null] }));
@@ -0,0 +1,11 @@
1
+ export interface PsColorPickerProps {
2
+ className?: string;
3
+ disabled?: boolean;
4
+ helperText?: string;
5
+ hideLabel?: boolean;
6
+ label: string;
7
+ onChange: (value: string) => void;
8
+ swatches?: string[];
9
+ value: string;
10
+ }
11
+ export declare const PsColorPicker: ({ className, disabled, helperText, hideLabel, label, onChange, swatches, value, }: PsColorPickerProps) => import("react").JSX.Element;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsColorPicker = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const ps_core_components_1 = require("@pallasoft/ps-core-components");
7
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
8
+ const ps_icons_1 = require("@pallasoft/ps-icons");
9
+ const react_aria_components_1 = require("react-aria-components");
10
+ const ps_color_input_config_1 = require("../constants/ps-color-input-config");
11
+ const ps_color_service_1 = require("../services/ps-color-service");
12
+ const PsColorPicker = ({ className, disabled, helperText, hideLabel, label, onChange, swatches = [...ps_color_input_config_1.PsColorInputConfig.swatches], value, }) => {
13
+ const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Dropdown);
14
+ const areaRef = (0, react_1.useRef)(null);
15
+ const [draft, setDraft] = (0, react_1.useState)(value);
16
+ const hsv = (0, react_1.useMemo)(() => ps_color_service_1.PsColorService.toHsv(value), [value]);
17
+ const swatchColor = ps_color_service_1.PsColorService.normaliseHex(value);
18
+ (0, react_1.useEffect)(() => {
19
+ setDraft(value);
20
+ }, [value]);
21
+ const commitHex = (next) => {
22
+ const hex = ps_color_service_1.PsColorService.normaliseHex(next);
23
+ if (hex)
24
+ onChange(hex);
25
+ else
26
+ setDraft(value);
27
+ };
28
+ const pickFromArea = (event) => {
29
+ const area = areaRef.current;
30
+ if (!area)
31
+ return;
32
+ const bounds = area.getBoundingClientRect();
33
+ const x = Math.min(Math.max(event.clientX - bounds.left, 0), bounds.width);
34
+ const y = Math.min(Math.max(event.clientY - bounds.top, 0), bounds.height);
35
+ onChange(ps_color_service_1.PsColorService.toHex({
36
+ brightness: Math.round(100 - (y / bounds.height) * 100),
37
+ hue: hsv.hue,
38
+ saturation: Math.round((x / bounds.width) * 100),
39
+ }));
40
+ };
41
+ return ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('flex flex-col gap-2', className), children: [hideLabel ? null : (0, jsx_runtime_1.jsx)("span", { className: 'ps-input-label block', children: label }), (0, jsx_runtime_1.jsxs)(react_aria_components_1.DialogTrigger, { children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.Button, { "aria-label": hideLabel ? label : undefined, className: 'ps-input-control ps-focus-ring flex min-h-11 w-full items-center gap-3 text-left text-sm font-semibold disabled:cursor-not-allowed disabled:opacity-60', isDisabled: disabled, children: [(0, jsx_runtime_1.jsx)("span", { className: 'h-6 w-6 shrink-0 rounded-md border border-line', style: { backgroundColor: swatchColor ? swatchColor : 'transparent' } }), (0, jsx_runtime_1.jsx)("span", { className: 'font-mono text-sm uppercase', children: swatchColor ? swatchColor : 'Pick a colour' }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'ml-auto block h-4 w-4 shrink-0 text-primary', iconKey: ps_icons_1.PsIconKey.Chevron })] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Popover, { className: 'ps-floating-panel w-[min(calc(100vw-2rem),18rem)] outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: (0, jsx_runtime_1.jsxs)(react_aria_components_1.Dialog, { "aria-label": label, className: 'flex flex-col gap-3 p-3 outline-none', children: [(0, jsx_runtime_1.jsx)("div", { className: 'relative h-36 w-full cursor-crosshair rounded-ps-control', onPointerDown: (event) => {
42
+ event.currentTarget.setPointerCapture(event.pointerId);
43
+ pickFromArea(event);
44
+ }, onPointerMove: (event) => {
45
+ if (event.buttons === 1)
46
+ pickFromArea(event);
47
+ }, ref: areaRef, role: 'presentation', style: {
48
+ background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${hsv.hue} 100% 50%))`,
49
+ }, children: (0, jsx_runtime_1.jsx)("span", { "aria-hidden": 'true', className: 'pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow', style: {
50
+ left: `${hsv.saturation}%`,
51
+ top: `${100 - hsv.brightness}%`,
52
+ backgroundColor: swatchColor ? swatchColor : 'transparent',
53
+ } }) }), (0, jsx_runtime_1.jsxs)("label", { className: 'flex flex-col gap-1', children: [(0, jsx_runtime_1.jsx)("span", { className: 'ps-input-helper', children: "Hue" }), (0, jsx_runtime_1.jsx)("input", { className: 'ps-focus-ring h-3 w-full cursor-pointer appearance-none rounded-full', max: 360, min: 0, onChange: (event) => onChange(ps_color_service_1.PsColorService.toHex({ ...hsv, hue: Number(event.target.value) })), step: 1, style: {
54
+ background: 'linear-gradient(to right, #FF0000, #FFFF00, #00FF00, #00FFFF, #0000FF, #FF00FF, #FF0000)',
55
+ }, type: 'range', value: hsv.hue })] }), (0, jsx_runtime_1.jsx)("input", { "aria-label": `${label} hex`, className: 'ps-input-control ps-focus-ring h-10 w-full font-mono text-sm uppercase', maxLength: 7, onBlur: (event) => commitHex(event.target.value), onChange: (event) => setDraft(event.target.value), onKeyDown: (event) => {
56
+ if (event.key !== 'Enter')
57
+ return;
58
+ event.preventDefault();
59
+ commitHex(draft);
60
+ }, placeholder: '#000000', value: draft }), (0, jsx_runtime_1.jsx)("div", { className: 'flex flex-wrap gap-1.5', children: swatches.map((swatch) => ((0, jsx_runtime_1.jsx)("button", { "aria-label": swatch, className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring h-7 w-7 rounded-full ring-offset-2 ring-offset-surface transition-shadow', swatch.toUpperCase() === (swatchColor ? swatchColor : '')
61
+ ? 'ring-2 ring-ink/70'
62
+ : 'hover:ring-2 hover:ring-ink/25'), onClick: () => onChange(swatch.toUpperCase()), style: { backgroundColor: swatch }, type: 'button' }, swatch))) })] }) })] }), helperText ? (0, jsx_runtime_1.jsx)("span", { className: 'ps-input-helper', children: helperText }) : null] }));
63
+ };
64
+ exports.PsColorPicker = PsColorPicker;
@@ -0,0 +1,7 @@
1
+ export declare const PsAmountInputConfig: {
2
+ readonly closeBracket: ")";
3
+ readonly disallowed: RegExp;
4
+ readonly minus: "-";
5
+ readonly openBracket: "(";
6
+ readonly point: ".";
7
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsAmountInputConfig = void 0;
4
+ exports.PsAmountInputConfig = {
5
+ closeBracket: ')',
6
+ disallowed: /[^0-9.]/g,
7
+ minus: '-',
8
+ openBracket: '(',
9
+ point: '.',
10
+ };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './components/ps-cascader-input';
2
+ export * from './components/ps-amount-input';
2
3
  export * from './components/ps-checkbox';
3
4
  export * from './components/ps-color-input';
5
+ export * from './components/ps-color-picker';
4
6
  export * from './components/ps-combobox-input';
5
7
  export * from './components/ps-date-input';
6
8
  export * from './components/ps-dropdown';
@@ -11,3 +13,6 @@ export * from './components/ps-select-input';
11
13
  export * from './components/ps-text-area-input';
12
14
  export * from './components/ps-text-input';
13
15
  export * from './components/ps-toggle';
16
+ export * from './services/ps-amount-input-service';
17
+ export * from './services/ps-color-service';
18
+ export * from './interfaces/color-hsv';
package/dist/index.js CHANGED
@@ -15,8 +15,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./components/ps-cascader-input"), exports);
18
+ __exportStar(require("./components/ps-amount-input"), exports);
18
19
  __exportStar(require("./components/ps-checkbox"), exports);
19
20
  __exportStar(require("./components/ps-color-input"), exports);
21
+ __exportStar(require("./components/ps-color-picker"), exports);
20
22
  __exportStar(require("./components/ps-combobox-input"), exports);
21
23
  __exportStar(require("./components/ps-date-input"), exports);
22
24
  __exportStar(require("./components/ps-dropdown"), exports);
@@ -27,3 +29,6 @@ __exportStar(require("./components/ps-select-input"), exports);
27
29
  __exportStar(require("./components/ps-text-area-input"), exports);
28
30
  __exportStar(require("./components/ps-text-input"), exports);
29
31
  __exportStar(require("./components/ps-toggle"), exports);
32
+ __exportStar(require("./services/ps-amount-input-service"), exports);
33
+ __exportStar(require("./services/ps-color-service"), exports);
34
+ __exportStar(require("./interfaces/color-hsv"), exports);
@@ -0,0 +1,5 @@
1
+ export interface PsColorHsv {
2
+ brightness: number;
3
+ hue: number;
4
+ saturation: number;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export declare class PsAmountInputService {
2
+ static normalise(raw: string): string;
3
+ static toNumber(raw: string): number;
4
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsAmountInputService = void 0;
4
+ const ps_amount_input_config_1 = require("../constants/ps-amount-input-config");
5
+ class PsAmountInputService {
6
+ static normalise(raw) {
7
+ const trimmed = raw.trim();
8
+ const isBracketed = trimmed.startsWith(ps_amount_input_config_1.PsAmountInputConfig.openBracket) &&
9
+ trimmed.endsWith(ps_amount_input_config_1.PsAmountInputConfig.closeBracket);
10
+ const isNegative = isBracketed || trimmed.startsWith(ps_amount_input_config_1.PsAmountInputConfig.minus);
11
+ const kept = trimmed.replace(ps_amount_input_config_1.PsAmountInputConfig.disallowed, '');
12
+ const pointIndex = kept.indexOf(ps_amount_input_config_1.PsAmountInputConfig.point);
13
+ const digits = pointIndex === -1
14
+ ? kept
15
+ : `${kept.slice(0, pointIndex + 1)}${kept
16
+ .slice(pointIndex + 1)
17
+ .split(ps_amount_input_config_1.PsAmountInputConfig.point)
18
+ .join('')}`;
19
+ if (digits === '')
20
+ return isNegative && !isBracketed ? ps_amount_input_config_1.PsAmountInputConfig.minus : '';
21
+ return isNegative ? `${ps_amount_input_config_1.PsAmountInputConfig.minus}${digits}` : digits;
22
+ }
23
+ static toNumber(raw) {
24
+ const normalised = PsAmountInputService.normalise(raw);
25
+ const parsed = Number(normalised);
26
+ return normalised === '' || !Number.isFinite(parsed) ? 0 : parsed;
27
+ }
28
+ }
29
+ exports.PsAmountInputService = PsAmountInputService;
@@ -0,0 +1,7 @@
1
+ import { PsColorHsv } from '../interfaces/color-hsv';
2
+ export declare class PsColorService {
3
+ static normaliseHex(value: string): string | null;
4
+ static toHsv(value: string): PsColorHsv;
5
+ static toHex({ brightness, hue, saturation }: PsColorHsv): string;
6
+ static readableTextOn(value: string): string;
7
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsColorService = void 0;
4
+ class PsColorService {
5
+ static normaliseHex(value) {
6
+ const hex = value.trim().replace('#', '');
7
+ if (/^[0-9a-fA-F]{3}$/.test(hex))
8
+ return `#${hex
9
+ .split('')
10
+ .map((character) => character + character)
11
+ .join('')
12
+ .toUpperCase()}`;
13
+ return /^[0-9a-fA-F]{6}$/.test(hex) ? `#${hex.toUpperCase()}` : null;
14
+ }
15
+ static toHsv(value) {
16
+ const hex = PsColorService.normaliseHex(value);
17
+ if (!hex)
18
+ return { brightness: 100, hue: 0, saturation: 0 };
19
+ const red = parseInt(hex.slice(1, 3), 16) / 255;
20
+ const green = parseInt(hex.slice(3, 5), 16) / 255;
21
+ const blue = parseInt(hex.slice(5, 7), 16) / 255;
22
+ const max = Math.max(red, green, blue);
23
+ const min = Math.min(red, green, blue);
24
+ const delta = max - min;
25
+ let hue = 0;
26
+ if (delta !== 0) {
27
+ if (max === red)
28
+ hue = ((green - blue) / delta) % 6;
29
+ else if (max === green)
30
+ hue = (blue - red) / delta + 2;
31
+ else
32
+ hue = (red - green) / delta + 4;
33
+ }
34
+ return {
35
+ brightness: Math.round(max * 100),
36
+ hue: Math.round(((hue * 60) % 360) + (hue < 0 ? 360 : 0)),
37
+ saturation: Math.round((max === 0 ? 0 : delta / max) * 100),
38
+ };
39
+ }
40
+ static toHex({ brightness, hue, saturation }) {
41
+ const value = brightness / 100;
42
+ const chroma = (saturation / 100) * value;
43
+ const second = chroma * (1 - Math.abs(((hue / 60) % 2) - 1));
44
+ const match = value - chroma;
45
+ const sector = Math.floor(hue / 60) % 6;
46
+ const channels = [
47
+ [chroma, second, 0],
48
+ [second, chroma, 0],
49
+ [0, chroma, second],
50
+ [0, second, chroma],
51
+ [second, 0, chroma],
52
+ [chroma, 0, second],
53
+ ][sector];
54
+ return `#${channels
55
+ .map((channel) => Math.round((channel + match) * 255)
56
+ .toString(16)
57
+ .padStart(2, '0'))
58
+ .join('')
59
+ .toUpperCase()}`;
60
+ }
61
+ static readableTextOn(value) {
62
+ const hex = PsColorService.normaliseHex(value);
63
+ if (!hex)
64
+ return '#0F172A';
65
+ const channel = (part) => {
66
+ const normalised = part / 255;
67
+ return normalised <= 0.03928
68
+ ? normalised / 12.92
69
+ : Math.pow((normalised + 0.055) / 1.055, 2.4);
70
+ };
71
+ const luminance = 0.2126 * channel(parseInt(hex.slice(1, 3), 16)) +
72
+ 0.7152 * channel(parseInt(hex.slice(3, 5), 16)) +
73
+ 0.0722 * channel(parseInt(hex.slice(5, 7), 16));
74
+ return 1.05 / (luminance + 0.05) >= 3 ? '#FFFFFF' : '#0F172A';
75
+ }
76
+ }
77
+ exports.PsColorService = PsColorService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-input-components",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "private": false,
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@internationalized/date": "^3.12.2",
32
- "@pallasoft/ps-core-components": "^2.2.0",
32
+ "@pallasoft/ps-core-components": "^2.3.1",
33
33
  "@pallasoft/ps-design-system": "^1.3.0",
34
34
  "@pallasoft/ps-icons": "^1.2.0",
35
35
  "@pallasoft/ps-utilities": "^2.0.0",