@pallasoft/ps-input-components 1.10.0 → 1.11.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.
Files changed (91) hide show
  1. package/dist/components/ps-cascader-input.d.ts +2 -1
  2. package/dist/components/ps-cascader-input.js +5 -4
  3. package/dist/components/ps-color-input.d.ts +2 -1
  4. package/dist/components/ps-color-input.js +3 -2
  5. package/dist/components/ps-combobox-input.d.ts +2 -1
  6. package/dist/components/ps-combobox-input.js +3 -2
  7. package/dist/components/ps-date-input.js +2 -1
  8. package/dist/components/ps-email-input.d.ts +3 -0
  9. package/dist/components/ps-email-input.js +17 -0
  10. package/dist/components/ps-multi-select-input.js +3 -2
  11. package/dist/components/ps-percent-input.d.ts +3 -0
  12. package/dist/components/ps-percent-input.js +16 -0
  13. package/dist/components/ps-phone-input.d.ts +14 -0
  14. package/dist/components/ps-phone-input.js +55 -0
  15. package/dist/components/ps-postal-code-input.d.ts +5 -0
  16. package/dist/components/ps-postal-code-input.js +14 -0
  17. package/dist/components/ps-required-mark.d.ts +4 -0
  18. package/dist/components/ps-required-mark.js +6 -0
  19. package/dist/components/ps-select-input.js +2 -1
  20. package/dist/components/ps-text-area-input.d.ts +1 -1
  21. package/dist/components/ps-text-area-input.js +2 -1
  22. package/dist/components/ps-text-input.js +2 -1
  23. package/dist/components/ps-url-input.d.ts +3 -0
  24. package/dist/components/ps-url-input.js +17 -0
  25. package/dist/constants/ps-phone-input-config.d.ts +10 -0
  26. package/dist/constants/ps-phone-input-config.js +13 -0
  27. package/dist/constants/ps-postal-code-formats.d.ts +5 -0
  28. package/dist/constants/ps-postal-code-formats.js +13 -0
  29. package/dist/constants/ps-text-format-config.d.ts +6 -0
  30. package/dist/constants/ps-text-format-config.js +9 -0
  31. package/dist/esm/components/ps-amount-input.js +10 -0
  32. package/dist/esm/components/ps-calendar-header.js +5 -0
  33. package/dist/esm/components/ps-cascader-input.js +45 -0
  34. package/dist/esm/components/ps-checkbox.js +7 -0
  35. package/dist/esm/components/ps-color-input.js +16 -0
  36. package/dist/esm/components/ps-color-picker.js +60 -0
  37. package/dist/esm/components/ps-combobox-input.js +10 -0
  38. package/dist/esm/components/ps-date-input.js +35 -0
  39. package/dist/esm/components/ps-dropdown.js +3 -0
  40. package/dist/esm/components/ps-email-input.js +13 -0
  41. package/dist/esm/components/ps-file-picker.js +27 -0
  42. package/dist/esm/components/ps-form-control.js +3 -0
  43. package/dist/esm/components/ps-multi-select-input.js +23 -0
  44. package/dist/esm/components/ps-percent-input.js +12 -0
  45. package/dist/esm/components/ps-phone-input.js +51 -0
  46. package/dist/esm/components/ps-postal-code-input.js +10 -0
  47. package/dist/esm/components/ps-required-mark.js +2 -0
  48. package/dist/esm/components/ps-select-input.js +22 -0
  49. package/dist/esm/components/ps-select-option-item.js +4 -0
  50. package/dist/esm/components/ps-text-area-input.js +4 -0
  51. package/dist/esm/components/ps-text-input.js +12 -0
  52. package/dist/esm/components/ps-toggle.js +3 -0
  53. package/dist/esm/components/ps-url-input.js +13 -0
  54. package/dist/esm/constants/ps-amount-input-config.js +7 -0
  55. package/dist/esm/constants/ps-color-input-config.js +13 -0
  56. package/dist/esm/constants/ps-date-input-config.js +8 -0
  57. package/dist/esm/constants/ps-phone-input-config.js +10 -0
  58. package/dist/esm/constants/ps-postal-code-formats.js +10 -0
  59. package/dist/esm/constants/ps-text-format-config.js +6 -0
  60. package/dist/esm/index.js +34 -0
  61. package/dist/esm/interfaces/color-hsv.js +1 -0
  62. package/dist/esm/interfaces/ps-highlight-part.js +1 -0
  63. package/dist/esm/interfaces/ps-phone-country-option.js +1 -0
  64. package/dist/esm/interfaces/ps-phone-display-data.js +1 -0
  65. package/dist/esm/interfaces/ps-postal-code-format.js +1 -0
  66. package/dist/esm/services/ps-amount-input-service.js +25 -0
  67. package/dist/esm/services/ps-cascader-service.js +22 -0
  68. package/dist/esm/services/ps-color-service.js +73 -0
  69. package/dist/esm/services/ps-combobox-input-service.js +6 -0
  70. package/dist/esm/services/ps-date-input-service.js +24 -0
  71. package/dist/esm/services/ps-phone-input-service.js +77 -0
  72. package/dist/esm/services/ps-postal-code-service.js +28 -0
  73. package/dist/esm/services/ps-select-input-service.js +8 -0
  74. package/dist/esm/services/ps-text-format-service.js +12 -0
  75. package/dist/index.d.ts +16 -0
  76. package/dist/index.js +16 -0
  77. package/dist/interfaces/ps-highlight-part.d.ts +4 -0
  78. package/dist/interfaces/ps-highlight-part.js +2 -0
  79. package/dist/interfaces/ps-phone-country-option.d.ts +5 -0
  80. package/dist/interfaces/ps-phone-country-option.js +2 -0
  81. package/dist/interfaces/ps-phone-display-data.d.ts +4 -0
  82. package/dist/interfaces/ps-phone-display-data.js +2 -0
  83. package/dist/interfaces/ps-postal-code-format.d.ts +5 -0
  84. package/dist/interfaces/ps-postal-code-format.js +2 -0
  85. package/dist/services/ps-phone-input-service.d.ts +15 -0
  86. package/dist/services/ps-phone-input-service.js +81 -0
  87. package/dist/services/ps-postal-code-service.d.ts +4 -0
  88. package/dist/services/ps-postal-code-service.js +32 -0
  89. package/dist/services/ps-text-format-service.d.ts +4 -0
  90. package/dist/services/ps-text-format-service.js +16 -0
  91. package/package.json +6 -2
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId } from 'react';
3
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
+ import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
5
+ import { PsColorInputConfig } from '../constants/ps-color-input-config';
6
+ import { PsColorPicker } from './ps-color-picker';
7
+ import { PsRequiredMark } from './ps-required-mark';
8
+ export const PsColorInput = ({ className, helperText, label, onChange, required, swatches = [...PsColorInputConfig.swatches], value, }) => {
9
+ const labelId = useId();
10
+ const swatchesId = useId();
11
+ const normalised = value.toUpperCase();
12
+ return (_jsxs("div", { className: PallasoftClassNameService.merge('flex flex-col gap-2', className), children: [_jsxs("span", { className: 'ps-input-label', id: labelId, children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsxs("div", { className: 'flex flex-col gap-4 rounded-ps-control border border-line bg-surface p-4', children: [_jsx(PsColorPicker, { hideLabel: true, label: `${label} picker`, onChange: onChange, swatches: swatches, value: value }), _jsxs("div", { className: 'flex flex-col gap-2 border-t border-line pt-4', children: [_jsx("span", { className: 'ps-input-helper', id: swatchesId, children: "Presets" }), _jsx("div", { "aria-labelledby": swatchesId, className: 'flex flex-wrap gap-2', role: 'radiogroup', children: swatches.map((swatch) => {
13
+ const isSelected = swatch.toUpperCase() === normalised;
14
+ return (_jsx("button", { "aria-checked": isSelected, "aria-label": swatch, className: 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 ? (_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Check })) : null }, swatch));
15
+ }) })] })] }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
16
+ };
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState, } from 'react';
3
+ import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
4
+ import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
5
+ import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
6
+ import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
7
+ import { PsColorInputConfig } from '../constants/ps-color-input-config';
8
+ import { PsColorService } from '../services/ps-color-service';
9
+ export const PsColorPicker = ({ className, disabled, helperText, hideLabel, label, onChange, swatches = [...PsColorInputConfig.swatches], value, }) => {
10
+ const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
11
+ const areaRef = useRef(null);
12
+ const [draft, setDraft] = useState(value);
13
+ const hsv = useMemo(() => PsColorService.toHsv(value), [value]);
14
+ const swatchColor = PsColorService.normaliseHex(value);
15
+ useEffect(() => {
16
+ setDraft(value);
17
+ }, [value]);
18
+ const commitHex = (next) => {
19
+ const hex = PsColorService.normaliseHex(next);
20
+ if (hex)
21
+ onChange(hex);
22
+ else
23
+ setDraft(value);
24
+ };
25
+ const pickFromArea = (event) => {
26
+ const area = areaRef.current;
27
+ if (!area)
28
+ return;
29
+ const bounds = area.getBoundingClientRect();
30
+ const x = Math.min(Math.max(event.clientX - bounds.left, 0), bounds.width);
31
+ const y = Math.min(Math.max(event.clientY - bounds.top, 0), bounds.height);
32
+ onChange(PsColorService.toHex({
33
+ brightness: Math.round(100 - (y / bounds.height) * 100),
34
+ hue: hsv.hue,
35
+ saturation: Math.round((x / bounds.width) * 100),
36
+ }));
37
+ };
38
+ return (_jsxs("div", { className: PallasoftClassNameService.merge('flex flex-col gap-2', className), children: [hideLabel ? null : _jsx("span", { className: 'ps-input-label block', children: label }), _jsxs(DialogTrigger, { children: [_jsxs(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: [_jsx("span", { className: 'h-6 w-6 shrink-0 rounded-md border border-line', style: { backgroundColor: swatchColor ? swatchColor : 'transparent' } }), _jsx("span", { className: 'font-mono text-sm uppercase', children: swatchColor ? swatchColor : 'Pick a colour' }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'ml-auto block h-4 w-4 shrink-0 text-primary', iconKey: PsIconKey.Chevron })] }), _jsx(Popover, { className: 'ps-floating-panel w-[min(calc(100vw-2rem),18rem)] outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsxs(Dialog, { "aria-label": label, className: 'flex flex-col gap-3 p-3 outline-none', children: [_jsx("div", { className: 'relative h-36 w-full cursor-crosshair rounded-ps-control', onPointerDown: (event) => {
39
+ event.currentTarget.setPointerCapture(event.pointerId);
40
+ pickFromArea(event);
41
+ }, onPointerMove: (event) => {
42
+ if (event.buttons === 1)
43
+ pickFromArea(event);
44
+ }, ref: areaRef, role: 'presentation', style: {
45
+ background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${hsv.hue} 100% 50%))`,
46
+ }, children: _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: {
47
+ left: `${hsv.saturation}%`,
48
+ top: `${100 - hsv.brightness}%`,
49
+ backgroundColor: swatchColor ? swatchColor : 'transparent',
50
+ } }) }), _jsxs("label", { className: 'flex flex-col gap-1', children: [_jsx("span", { className: 'ps-input-helper', children: "Hue" }), _jsx("input", { className: 'ps-focus-ring h-3 w-full cursor-pointer appearance-none rounded-full', max: 360, min: 0, onChange: (event) => onChange(PsColorService.toHex({ ...hsv, hue: Number(event.target.value) })), step: 1, style: {
51
+ background: 'linear-gradient(to right, #FF0000, #FFFF00, #00FF00, #00FFFF, #0000FF, #FF00FF, #FF0000)',
52
+ }, type: 'range', value: hsv.hue })] }), _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) => {
53
+ if (event.key !== 'Enter')
54
+ return;
55
+ event.preventDefault();
56
+ commitHex(draft);
57
+ }, placeholder: '#000000', value: draft }), _jsx("div", { className: 'flex flex-wrap gap-1.5', children: swatches.map((swatch) => (_jsx("button", { "aria-label": swatch, className: PallasoftClassNameService.merge('ps-focus-ring h-7 w-7 rounded-full ring-offset-2 ring-offset-surface transition-shadow', swatch.toUpperCase() === (swatchColor ? swatchColor : '')
58
+ ? 'ring-2 ring-ink/70'
59
+ : 'hover:ring-2 hover:ring-ink/25'), onClick: () => onChange(swatch.toUpperCase()), style: { backgroundColor: swatch }, type: 'button' }, swatch))) })] }) })] }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
60
+ };
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PsSpinner } from '@pallasoft/ps-core-components';
3
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
+ import { ComboBox, Input, Label, ListBox, ListBoxItem, Popover } from 'react-aria-components';
5
+ import { PsComboboxInputService } from '../services/ps-combobox-input-service';
6
+ import { PsRequiredMark } from './ps-required-mark';
7
+ export const PsComboboxInput = ({ className, getItemKey, getItemLabel, isLoading = false, items, label, onInputChange, onSelectionChange, placeholder, renderItem, required, }) => {
8
+ const itemsByKey = new Map(items.map((item) => [getItemKey(item), item]));
9
+ return (_jsxs(ComboBox, { className: PallasoftClassNameService.merge('block', className), defaultFilter: () => true, items: items, onInputChange: onInputChange, onSelectionChange: (key) => onSelectionChange?.(key === null ? null : PsComboboxInputService.getItem(itemsByKey, key)), children: [label ? (_jsxs(Label, { className: 'ps-input-label', children: [label, _jsx(PsRequiredMark, { isRequired: required })] })) : null, _jsx("span", { className: 'relative mt-2 block', children: _jsx(Input, { className: 'ps-input-control ps-focus-ring min-h-11 w-full text-sm font-semibold', placeholder: placeholder }) }), _jsx(Popover, { className: 'w-[--trigger-width] rounded-2xl border border-line bg-white/95 p-1.5 shadow-lg dark:border-white/10 dark:bg-slate-900/95', children: isLoading ? (_jsx("div", { className: 'flex items-center justify-center py-4', children: _jsx(PsSpinner, { label: 'Loading results', size: 'small' }) })) : (_jsx(ListBox, { className: 'max-h-72 overflow-y-auto outline-none', items: items, children: (item) => (_jsx(ListBoxItem, { className: 'ps-focus-ring cursor-pointer rounded-xl px-3 py-2 text-sm font-semibold text-ink selected:bg-primary-soft selected:text-primary hover:bg-slate-50 dark:hover:bg-white/6', id: getItemKey(item), textValue: getItemLabel(item), children: renderItem ? renderItem(item) : getItemLabel(item) })) })) })] }));
10
+ };
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useState } from 'react';
3
+ import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
4
+ import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
5
+ import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
6
+ import DatePicker from 'react-datepicker';
7
+ import { PsDateInputConfig } from '../constants/ps-date-input-config';
8
+ import { PsDateInputService } from '../services/ps-date-input-service';
9
+ import { PsCalendarHeader } from './ps-calendar-header';
10
+ import { PsRequiredMark } from './ps-required-mark';
11
+ export const PsDateInput = ({ className, defaultValue, disabled, helperText, hideLabel, label, max, min, name, onChange, placeholder = PsDateInputConfig.placeholder, required, value, }) => {
12
+ const inputId = useId();
13
+ const portalId = `${PsDateInputConfig.portalId}-${inputId.replace(/:/g, '')}`;
14
+ const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
15
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue === undefined ? '' : defaultValue);
16
+ const currentValue = value === undefined ? uncontrolledValue : value;
17
+ useEffect(() => {
18
+ const existing = document.getElementById(portalId);
19
+ const container = existing ? existing : document.createElement('div');
20
+ container.id = portalId;
21
+ container.setAttribute(PsDateInputConfig.topLayerAttribute, 'true');
22
+ container.style.position = 'relative';
23
+ container.style.zIndex = String(zIndex);
24
+ if (!existing)
25
+ document.body.appendChild(container);
26
+ return () => container.remove();
27
+ }, [portalId, zIndex]);
28
+ const handleChange = (date) => {
29
+ const nextValue = PsDateInputService.format(date);
30
+ if (value === undefined)
31
+ setUncontrolledValue(nextValue);
32
+ onChange?.(nextValue);
33
+ };
34
+ return (_jsxs("div", { className: PallasoftClassNameService.merge('block', className), children: [_jsxs("label", { className: hideLabel ? 'sr-only' : 'ps-input-label block', htmlFor: inputId, children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsxs("div", { className: hideLabel ? 'relative' : 'relative mt-2', children: [_jsx(DatePicker, { autoComplete: 'off', calendarClassName: 'ps-floating-panel ps-datepicker', className: 'ps-input-control ps-focus-ring min-h-11 w-full pr-10 text-sm font-semibold', dateFormat: [...PsDateInputConfig.inputFormats], disabled: disabled, id: inputId, maxDate: PsDateInputService.parseBound(max), minDate: PsDateInputService.parseBound(min), onChange: handleChange, placeholderText: placeholder, popperClassName: 'ps-datepicker-popper', popperPlacement: 'bottom-start', popperProps: { strategy: 'fixed' }, portalId: portalId, renderCustomHeader: (headerProps) => _jsx(PsCalendarHeader, { ...headerProps }), required: required, selected: PsDateInputService.parse(currentValue), showPopperArrow: false, wrapperClassName: 'block w-full' }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/50', iconKey: PsIconKey.Calendar }), name ? _jsx("input", { name: name, type: 'hidden', value: currentValue }) : null] }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
35
+ };
@@ -0,0 +1,3 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PsSelectInput } from './ps-select-input';
3
+ export const PsDropdown = ({ className, defaultValue, disabled = false, helperText, label, name, onChange, options, placeholder, value, }) => (_jsx(PsSelectInput, { className: className, defaultValue: defaultValue, disabled: disabled, helperText: helperText, label: label, name: name, onChange: onChange, options: options, placeholder: placeholder, value: value }));
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PsTextFormatService } from '../services/ps-text-format-service';
3
+ import { PsTextInput } from './ps-text-input';
4
+ export const PsEmailInput = ({ onBlur, onChange, ...props }) => (_jsx(PsTextInput, { ...props, autoCapitalize: 'none', autoComplete: 'email', inputMode: 'email', onBlur: (event) => {
5
+ const formatted = PsTextFormatService.email(event.target.value);
6
+ if (formatted !== event.target.value) {
7
+ event.target.value = formatted;
8
+ if (onChange)
9
+ onChange(event);
10
+ }
11
+ if (onBlur)
12
+ onBlur(event);
13
+ }, onChange: onChange, spellCheck: false, type: 'email' }));
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
+ import { PsIcon } from '@pallasoft/ps-icons';
5
+ export const PsFilePicker = ({ accept, acceptLabel, className, disabled, dropLabel, maxSizeBytes, maxSizeLabel, onError, onFileLoad, }) => {
6
+ const acceptAttr = useMemo(() => accept.join(','), [accept]);
7
+ const isAccepted = (candidate) => accept.some((entry) => entry.startsWith('.')
8
+ ? candidate.name.toLowerCase().endsWith(entry.toLowerCase())
9
+ : entry === candidate.type);
10
+ const onFileChange = (event) => {
11
+ const newFile = event.target.files?.[0];
12
+ if (!newFile || disabled)
13
+ return;
14
+ if (!isAccepted(newFile)) {
15
+ onError?.('Invalid file type. Please select a supported file.');
16
+ event.target.value = '';
17
+ return;
18
+ }
19
+ if (maxSizeBytes && newFile.size > maxSizeBytes) {
20
+ onError?.(maxSizeLabel ? `File is too large. ${maxSizeLabel}.` : 'File is too large.');
21
+ event.target.value = '';
22
+ return;
23
+ }
24
+ onFileLoad(newFile);
25
+ };
26
+ return (_jsxs("div", { className: PallasoftClassNameService.merge('relative flex h-48 w-full flex-col items-center justify-center rounded-lg border-2 border-dashed border-line p-4 transition-all hover:border-primary/50 hover:bg-primary/5 sm:h-64', className), children: [_jsxs("div", { className: 'flex flex-col items-center gap-3 text-center', children: [_jsx(PsIcon, { iconKey: 'upload', className: 'h-8 w-8 text-primary' }), _jsxs("div", { children: [_jsx("p", { className: 'text-body-2 font-medium text-ink sm:text-body-1', children: dropLabel }), _jsx("p", { className: 'mt-1 text-body-3 text-ink/60 sm:text-body-2', children: "or click to browse your files" }), maxSizeLabel ? _jsx("p", { className: 'mt-3 text-body-3 text-ink/60', children: maxSizeLabel }) : null, _jsxs("p", { className: 'text-body-3 text-ink/60', children: ["Accepted formats: ", acceptLabel] })] })] }), _jsx("input", { type: 'file', accept: acceptAttr, className: 'absolute inset-0 h-full w-full cursor-pointer opacity-0', "aria-label": 'File upload', onChange: onFileChange, disabled: disabled, "aria-disabled": disabled })] }));
27
+ };
@@ -0,0 +1,3 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
+ export const PsFormControl = ({ children, className, errorText, helperText, label, }) => (_jsxs("div", { className: PallasoftClassNameService.merge('grid gap-2', className), children: [label ? _jsx("span", { className: 'ps-input-label-compact', children: label }) : null, children, errorText ? (_jsx("p", { className: 'text-xs font-semibold text-danger', children: errorText })) : helperText ? (_jsx("p", { className: 'ps-input-helper', children: helperText })) : null] }));
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
4
+ import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
5
+ import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
6
+ import { Button, Label, ListBox, Popover, Select, SelectValue, Text, } from 'react-aria-components';
7
+ import { PsRequiredMark } from './ps-required-mark';
8
+ import { PsSelectOptionItem } from './ps-select-option-item';
9
+ export const PsMultiSelectInput = ({ className, defaultValue, disabled, helperText, hideLabel, label, name, onChange, options, placeholder = 'Select options', required, value, }) => {
10
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue === undefined ? [] : defaultValue);
11
+ const currentValue = value === undefined ? uncontrolledValue : value;
12
+ const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
13
+ const labelByValue = new Map(options.map((option) => [option.value, option.label]));
14
+ const handleChange = (keys) => {
15
+ const nextValue = keys.map((key) => String(key));
16
+ if (value === undefined)
17
+ setUncontrolledValue(nextValue);
18
+ onChange?.(nextValue);
19
+ };
20
+ return (_jsxs(Select, { "aria-label": hideLabel ? label : undefined, className: PallasoftClassNameService.merge('block', className), isDisabled: disabled, isRequired: required, name: name, onChange: handleChange, placeholder: placeholder, selectionMode: 'multiple', value: currentValue.filter((selected) => labelByValue.has(selected)), children: [hideLabel ? null : (_jsxs(Label, { className: 'ps-input-label block', children: [label, _jsx(PsRequiredMark, { isRequired: required })] })), _jsxs(Button, { className: PallasoftClassNameService.merge('ps-input-control ps-focus-ring relative flex min-h-11 w-full items-center py-1.5 pr-10 text-left text-sm font-semibold disabled:cursor-not-allowed disabled:opacity-60', hideLabel ? '' : 'mt-2'), children: [_jsx(SelectValue, { className: 'flex min-w-0 flex-1 flex-wrap gap-1.5', children: ({ isPlaceholder }) => isPlaceholder ? (_jsx("span", { className: 'font-medium text-ink/45', children: placeholder })) : (currentValue
21
+ .filter((selected) => labelByValue.has(selected))
22
+ .map((selected) => (_jsx("span", { className: 'inline-flex max-w-full items-center rounded-full bg-primary/12 px-2.5 py-0.5 text-xs font-semibold text-ink', children: _jsx("span", { className: 'truncate', children: labelByValue.get(selected) }) }, selected)))) }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-primary', iconKey: PsIconKey.Chevron })] }), helperText ? (_jsx(Text, { className: 'ps-input-helper', slot: 'description', children: helperText })) : null, _jsx(Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-72 min-w-[var(--trigger-width)] overflow-y-auto p-1.5 outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(ListBox, { className: 'outline-none', items: options.map((option) => ({ ...option, id: option.value })), children: (option) => _jsx(PsSelectOptionItem, { option: option }) }) })] }));
23
+ };
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
+ import { PsTextFormatConfig } from '../constants/ps-text-format-config';
4
+ import { PsAmountInputService } from '../services/ps-amount-input-service';
5
+ import { PsTextInput } from './ps-text-input';
6
+ export const PsPercentInput = ({ className, onChange, ...props }) => (_jsxs("div", { className: 'relative', children: [_jsx(PsTextInput, { ...props, autoComplete: 'off', className: PallasoftClassNameService.merge('pr-8', className), inputMode: 'decimal', onChange: (event) => {
7
+ const normalised = PsAmountInputService.normalise(event.target.value);
8
+ if (normalised !== event.target.value)
9
+ event.target.value = normalised;
10
+ if (onChange)
11
+ onChange(event);
12
+ }, type: 'text' }), _jsx("span", { "aria-hidden": 'true', className: 'pointer-events-none absolute right-3 bottom-3 text-sm text-ink/50', children: PsTextFormatConfig.percentSuffix })] }));
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useMemo, useState } from 'react';
3
+ import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
4
+ import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
5
+ import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
6
+ import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
7
+ import { PsPhoneInputConfig } from '../constants/ps-phone-input-config';
8
+ import { PsPhoneInputService } from '../services/ps-phone-input-service';
9
+ import { PsRequiredMark } from './ps-required-mark';
10
+ export const PsPhoneInput = ({ className, defaultCountry = PsPhoneInputConfig.defaultCountry, disabled, helperText, hideLabel, label, name, onBlur, onChange, required, value, }) => {
11
+ const inputId = useId();
12
+ const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
13
+ const initial = PsPhoneInputService.display(value, defaultCountry);
14
+ const [country, setCountry] = useState(initial.country);
15
+ const [national, setNational] = useState(initial.national);
16
+ const [isTouched, setIsTouched] = useState(false);
17
+ const [isOpen, setIsOpen] = useState(false);
18
+ const [query, setQuery] = useState('');
19
+ const countries = useMemo(() => PsPhoneInputService.countries(defaultCountry), [defaultCountry]);
20
+ const matches = useMemo(() => PsPhoneInputService.search(countries, query), [countries, query]);
21
+ useEffect(() => {
22
+ if (PsPhoneInputService.toStored(national, country) === value)
23
+ return;
24
+ const next = PsPhoneInputService.display(value, country);
25
+ setCountry(next.country);
26
+ setNational(next.national);
27
+ }, [value]);
28
+ const emit = (nextNational, nextCountry) => onChange(PsPhoneInputService.toStored(nextNational, nextCountry));
29
+ const isInvalid = isTouched && !PsPhoneInputService.isValid(national, country);
30
+ const choose = (code) => {
31
+ setCountry(code);
32
+ setIsOpen(false);
33
+ setQuery('');
34
+ emit(national, code);
35
+ document.getElementById(inputId)?.focus();
36
+ };
37
+ return (_jsxs("div", { className: PallasoftClassNameService.merge('block', className), children: [_jsxs("label", { className: hideLabel ? 'sr-only' : 'ps-input-label', htmlFor: inputId, children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsxs("div", { className: PallasoftClassNameService.merge('ps-input-control flex min-h-11 items-stretch overflow-hidden p-0 focus-within:border-primary', hideLabel ? '' : 'mt-2', isInvalid ? 'border-danger bg-danger/5' : '', disabled ? 'opacity-60' : ''), children: [_jsxs(DialogTrigger, { isOpen: isOpen, onOpenChange: setIsOpen, children: [_jsxs(Button, { "aria-label": PsPhoneInputConfig.countryLabel, className: 'ps-focus-ring flex shrink-0 items-center gap-1.5 border-r border-line px-3 text-sm font-semibold text-ink', isDisabled: disabled, children: [country, _jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-3.5 w-3.5 text-ink/45', iconKey: PsIconKey.Chevron })] }), _jsx(Popover, { className: 'ps-floating-panel w-72 outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsxs(Dialog, { "aria-label": PsPhoneInputConfig.countryLabel, className: 'flex flex-col outline-none', children: [_jsx("div", { className: 'border-b border-line p-2', children: _jsx("input", { "aria-label": PsPhoneInputConfig.searchPlaceholder, autoFocus: true, className: 'ps-input-control ps-focus-ring h-10 w-full text-sm', onChange: (event) => setQuery(event.target.value), placeholder: PsPhoneInputConfig.searchPlaceholder, type: 'search', value: query }) }), _jsx("ul", { className: 'ps-scrollbar-hidden max-h-64 overflow-y-auto p-1.5', role: 'listbox', children: matches.length === 0 ? (_jsx("li", { className: 'px-3 py-2.5 text-sm text-ink/50', children: PsPhoneInputConfig.emptyMessage })) : (matches.map((option) => (_jsx("li", { role: 'none', children: _jsxs("button", { "aria-selected": option.code === country, className: PallasoftClassNameService.merge('ps-focus-ring flex w-full items-center justify-between gap-3 rounded-ps-control px-3 py-2.5 text-left text-sm', option.code === country
38
+ ? 'bg-primary/10 text-ink'
39
+ : 'text-ink/80 hover:bg-mist/60 hover:text-ink'), onClick: () => choose(option.code), role: 'option', type: 'button', children: [_jsx("span", { className: 'min-w-0 truncate', children: PsPhoneInputService.highlight(option.label, query).map((part, index) => part.isMatch ? (_jsx("strong", { className: 'font-semibold text-ink', children: part.text }, index)) : (_jsx("span", { children: part.text }, index))) }), _jsxs("span", { className: 'shrink-0 text-xs text-ink/50', children: ["+", option.callingCode] })] }) }, option.code)))) })] }) })] }), _jsxs("span", { "aria-hidden": 'true', className: 'flex items-center pl-3 text-sm text-ink/45', children: [PsPhoneInputConfig.plus, PsPhoneInputService.callingCode(country)] }), _jsx("input", { "aria-invalid": isInvalid, autoComplete: 'tel-national', className: 'min-w-0 flex-1 bg-transparent px-2 text-sm text-ink outline-none', disabled: disabled, id: inputId, inputMode: 'tel', name: name, onBlur: () => {
40
+ setIsTouched(true);
41
+ if (onBlur)
42
+ onBlur();
43
+ }, onChange: (event) => {
44
+ const isDeleting = event.target.value.length < national.length;
45
+ const next = isDeleting
46
+ ? event.target.value
47
+ : PsPhoneInputService.format(event.target.value, country);
48
+ setNational(next);
49
+ emit(next, country);
50
+ }, required: required, type: 'tel', value: national })] }), isInvalid ? (_jsx("span", { className: 'ps-input-helper text-danger', children: PsPhoneInputConfig.invalidMessage })) : helperText ? (_jsx("span", { className: 'ps-input-helper', children: helperText })) : null] }));
51
+ };
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PsPostalCodeService } from '../services/ps-postal-code-service';
3
+ import { PsTextInput } from './ps-text-input';
4
+ export const PsPostalCodeInput = ({ country, onChange, ...props }) => (_jsx(PsTextInput, { ...props, autoComplete: 'postal-code', autoCapitalize: 'characters', maxLength: PsPostalCodeService.maxLength(country), onChange: (event) => {
5
+ const formatted = PsPostalCodeService.format(event.target.value, country);
6
+ if (formatted !== event.target.value)
7
+ event.target.value = formatted;
8
+ if (onChange)
9
+ onChange(event);
10
+ }, type: 'text' }));
@@ -0,0 +1,2 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export const PsRequiredMark = ({ isRequired }) => isRequired ? (_jsx("span", { "aria-hidden": 'true', className: 'ml-0.5 text-danger', children: "*" })) : null;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
4
+ import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
5
+ import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
6
+ import { Button, Label, ListBox, Popover, Select, SelectValue, Text, } from 'react-aria-components';
7
+ import { PsSelectInputService } from '../services/ps-select-input-service';
8
+ import { PsRequiredMark } from './ps-required-mark';
9
+ import { PsSelectOptionItem } from './ps-select-option-item';
10
+ export const PsSelectInput = ({ className, defaultValue, disabled, helperText, hideLabel, label, name, onChange, options, placeholder = 'Select an option', required, value, }) => {
11
+ const [uncontrolledValue, setUncontrolledValue] = useState(() => PsSelectInputService.getDefaultValue(defaultValue, options));
12
+ const currentValue = value === undefined ? uncontrolledValue : value;
13
+ const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
14
+ const hasOption = options.some((option) => option.value === currentValue);
15
+ const handleChange = (key) => {
16
+ const nextValue = key === null ? '' : String(key);
17
+ if (value === undefined)
18
+ setUncontrolledValue(nextValue);
19
+ onChange?.(nextValue);
20
+ };
21
+ return (_jsxs(Select, { "aria-label": hideLabel ? label : undefined, className: PallasoftClassNameService.merge('block', className), isDisabled: disabled, isRequired: required, name: name, onChange: handleChange, placeholder: placeholder, value: hasOption ? currentValue : null, children: [hideLabel ? null : (_jsxs(Label, { className: 'ps-input-label block', children: [label, _jsx(PsRequiredMark, { isRequired: required })] })), _jsxs(Button, { className: PallasoftClassNameService.merge('ps-input-control ps-focus-ring relative flex min-h-11 w-full items-center pr-10 text-left text-sm font-semibold disabled:cursor-not-allowed disabled:opacity-60', hideLabel ? '' : 'mt-2'), children: [_jsx(SelectValue, { className: 'min-w-0 flex-1 truncate', children: ({ isPlaceholder, selectedText }) => isPlaceholder ? (_jsx("span", { className: 'font-medium text-ink/45', children: placeholder })) : (selectedText) }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-primary', iconKey: PsIconKey.Chevron })] }), helperText ? (_jsx(Text, { className: 'ps-input-helper', slot: 'description', children: helperText })) : null, _jsx(Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-72 min-w-[var(--trigger-width)] overflow-y-auto p-1.5 outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(ListBox, { className: 'outline-none', items: options.map((option) => ({ ...option, id: option.value })), children: (option) => _jsx(PsSelectOptionItem, { option: option }) }) })] }));
22
+ };
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
3
+ import { ListBoxItem } from 'react-aria-components';
4
+ export const PsSelectOptionItem = ({ option }) => (_jsx(ListBoxItem, { className: 'ps-floating-option text-ink', id: option.value, textValue: option.label, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsx("span", { className: 'flex h-4 w-4 shrink-0 items-center justify-center text-primary', children: isSelected ? (_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Check })) : null }), _jsxs("span", { className: 'min-w-0 flex-1', children: [_jsx("span", { className: 'block truncate', children: option.label }), option.description ? (_jsx("span", { className: 'mt-0.5 block text-xs font-normal text-ink/55', children: option.description })) : null] })] })) }));
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
+ import { PsRequiredMark } from './ps-required-mark';
4
+ export const PsTextAreaInput = ({ className, helperText, hideLabel, label, required, rows = 4, ...props }) => (_jsxs("label", { className: 'block', children: [_jsxs("span", { className: hideLabel ? 'sr-only' : 'ps-input-label', children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsx("textarea", { className: PallasoftClassNameService.merge(hideLabel ? 'ps-input-control' : 'ps-input-control mt-2', 'min-h-24 resize-y leading-6', className), required: required, rows: rows, ...props }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
+ import { PsDateInput } from './ps-date-input';
4
+ import { PsRequiredMark } from './ps-required-mark';
5
+ export const PsTextInput = ({ className, defaultValue, disabled, helperText, hideLabel, label, name, onChange, required, type, value, ...props }) => {
6
+ if (type === 'date') {
7
+ return (_jsx(PsDateInput, { className: className, defaultValue: typeof defaultValue === 'string' ? defaultValue : undefined, disabled: disabled, helperText: helperText, hideLabel: hideLabel, label: label, name: name, onChange: (nextValue) => onChange?.({
8
+ target: { name, value: nextValue },
9
+ }), required: required, value: typeof value === 'string' ? value : undefined }));
10
+ }
11
+ return (_jsxs("label", { className: 'block', children: [_jsxs("span", { className: hideLabel ? 'sr-only' : 'ps-input-label', children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsx("input", { className: PallasoftClassNameService.merge(hideLabel ? 'ps-input-control' : 'ps-input-control mt-2', className), defaultValue: defaultValue, disabled: disabled, name: name, onChange: onChange, required: required, type: type, value: value, ...props }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
12
+ };
@@ -0,0 +1,3 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
+ export const PsToggle = ({ description, isChecked, label, name, onChange }) => (_jsxs("label", { className: 'ps-focus-ring flex cursor-pointer items-center justify-between gap-4 rounded-2xl border border-line bg-white/78 px-4 py-3 text-ink transition-colors hover:border-primary/35 dark:border-white/10 dark:bg-white/6 dark:text-white', children: [_jsxs("span", { children: [_jsx("span", { className: 'block text-sm font-semibold text-current', children: label }), description ? (_jsx("span", { className: 'mt-1 block text-xs leading-5 text-slate-500 dark:text-slate-400', children: description })) : null] }), _jsx("input", { checked: isChecked, className: 'sr-only', name: name, onChange: (event) => onChange?.(event.target.checked), type: 'checkbox' }), _jsx("span", { className: PallasoftClassNameService.merge('flex h-7 w-12 items-center rounded-full p-1 transition', isChecked ? 'bg-primary' : 'bg-slate-200 dark:bg-slate-700'), children: _jsx("span", { className: PallasoftClassNameService.merge('h-5 w-5 rounded-full bg-white ring-1 ring-black/5 transition dark:ring-white/10', isChecked && 'translate-x-5') }) })] }));
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PsTextFormatService } from '../services/ps-text-format-service';
3
+ import { PsTextInput } from './ps-text-input';
4
+ export const PsUrlInput = ({ onBlur, onChange, ...props }) => (_jsx(PsTextInput, { ...props, autoCapitalize: 'none', autoComplete: 'url', inputMode: 'url', onBlur: (event) => {
5
+ const formatted = PsTextFormatService.url(event.target.value);
6
+ if (formatted !== event.target.value) {
7
+ event.target.value = formatted;
8
+ if (onChange)
9
+ onChange(event);
10
+ }
11
+ if (onBlur)
12
+ onBlur(event);
13
+ }, onChange: onChange, spellCheck: false, type: 'text' }));
@@ -0,0 +1,7 @@
1
+ export const PsAmountInputConfig = {
2
+ closeBracket: ')',
3
+ disallowed: /[^0-9.]/g,
4
+ minus: '-',
5
+ openBracket: '(',
6
+ point: '.',
7
+ };
@@ -0,0 +1,13 @@
1
+ export const PsColorInputConfig = {
2
+ hexPattern: /^#[0-9a-fA-F]{6}$/,
3
+ swatches: [
4
+ '#1F6FEB',
5
+ '#0E9F6E',
6
+ '#F59E0B',
7
+ '#E02424',
8
+ '#7E3AF2',
9
+ '#0F172A',
10
+ '#DB2777',
11
+ '#0891B2',
12
+ ],
13
+ };
@@ -0,0 +1,8 @@
1
+ export const PsDateInputConfig = {
2
+ inputFormats: ['MMM d, yyyy', 'yyyy-MM-dd', 'MM/dd/yyyy', 'd MMM yyyy'],
3
+ monthLabelFormat: 'MMMM YYYY',
4
+ valueFormat: 'YYYY-MM-DD',
5
+ placeholder: 'Select a date',
6
+ portalId: 'ps-floating-root',
7
+ topLayerAttribute: 'data-react-aria-top-layer',
8
+ };
@@ -0,0 +1,10 @@
1
+ export const PsPhoneInputConfig = {
2
+ countryLabel: 'Country',
3
+ defaultCountry: 'CA',
4
+ displayNamesType: 'region',
5
+ emptyMessage: 'No country matches that',
6
+ invalidMessage: 'Check this number for the country chosen.',
7
+ locale: 'en',
8
+ plus: '+',
9
+ searchPlaceholder: 'Search for a country',
10
+ };
@@ -0,0 +1,10 @@
1
+ export const PsPostalCodeFormats = {
2
+ CA: { allowed: /[^A-Z0-9]/g, groups: [3, 3], separator: ' ' },
3
+ GB: { allowed: /[^A-Z0-9]/g, groups: [], separator: ' ' },
4
+ IN: { allowed: /[^0-9]/g, groups: [6], separator: '' },
5
+ NL: { allowed: /[^A-Z0-9]/g, groups: [4, 2], separator: ' ' },
6
+ US: { allowed: /[^0-9]/g, groups: [5, 4], separator: '-' },
7
+ };
8
+ export const PsPostalCodeConfig = {
9
+ fallbackAllowed: /[^A-Z0-9 -]/g,
10
+ };
@@ -0,0 +1,6 @@
1
+ export const PsTextFormatConfig = {
2
+ httpsPrefix: 'https://',
3
+ percentSuffix: '%',
4
+ schemePattern: /^[a-z][a-z0-9+.-]*:\/\//i,
5
+ whitespace: /\s+/g,
6
+ };
@@ -0,0 +1,34 @@
1
+ export * from './components/ps-cascader-input';
2
+ export * from './components/ps-amount-input';
3
+ export * from './components/ps-checkbox';
4
+ export * from './components/ps-color-input';
5
+ export * from './components/ps-color-picker';
6
+ export * from './components/ps-combobox-input';
7
+ export * from './components/ps-date-input';
8
+ export * from './components/ps-dropdown';
9
+ export * from './components/ps-file-picker';
10
+ export * from './components/ps-form-control';
11
+ export * from './components/ps-multi-select-input';
12
+ export * from './components/ps-select-input';
13
+ export * from './components/ps-text-area-input';
14
+ export * from './components/ps-text-input';
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';
19
+ export * from './components/ps-email-input';
20
+ export * from './components/ps-percent-input';
21
+ export * from './components/ps-phone-input';
22
+ export * from './components/ps-postal-code-input';
23
+ export * from './components/ps-url-input';
24
+ export * from './constants/ps-phone-input-config';
25
+ export * from './constants/ps-postal-code-formats';
26
+ export * from './constants/ps-text-format-config';
27
+ export * from './interfaces/ps-highlight-part';
28
+ export * from './interfaces/ps-phone-country-option';
29
+ export * from './interfaces/ps-phone-display-data';
30
+ export * from './interfaces/ps-postal-code-format';
31
+ export * from './services/ps-phone-input-service';
32
+ export * from './services/ps-postal-code-service';
33
+ export * from './services/ps-text-format-service';
34
+ export * from './components/ps-required-mark';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { PsAmountInputConfig } from '../constants/ps-amount-input-config';
2
+ export class PsAmountInputService {
3
+ static normalise(raw) {
4
+ const trimmed = raw.trim();
5
+ const isBracketed = trimmed.startsWith(PsAmountInputConfig.openBracket) &&
6
+ trimmed.endsWith(PsAmountInputConfig.closeBracket);
7
+ const isNegative = isBracketed || trimmed.startsWith(PsAmountInputConfig.minus);
8
+ const kept = trimmed.replace(PsAmountInputConfig.disallowed, '');
9
+ const pointIndex = kept.indexOf(PsAmountInputConfig.point);
10
+ const digits = pointIndex === -1
11
+ ? kept
12
+ : `${kept.slice(0, pointIndex + 1)}${kept
13
+ .slice(pointIndex + 1)
14
+ .split(PsAmountInputConfig.point)
15
+ .join('')}`;
16
+ if (digits === '')
17
+ return isNegative && !isBracketed ? PsAmountInputConfig.minus : '';
18
+ return isNegative ? `${PsAmountInputConfig.minus}${digits}` : digits;
19
+ }
20
+ static toNumber(raw) {
21
+ const normalised = PsAmountInputService.normalise(raw);
22
+ const parsed = Number(normalised);
23
+ return normalised === '' || !Number.isFinite(parsed) ? 0 : parsed;
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+ export class PsCascaderService {
2
+ static findSelected(groups, value) {
3
+ if (value === '')
4
+ return null;
5
+ for (const group of groups) {
6
+ const option = group.options.find((item) => item.value === value);
7
+ if (option)
8
+ return { group, option };
9
+ }
10
+ return null;
11
+ }
12
+ static search(groups, query) {
13
+ const term = query.trim().toLowerCase();
14
+ if (term === '')
15
+ return [];
16
+ return groups.flatMap((group) => group.options
17
+ .filter((option) => option.label.toLowerCase().includes(term) ||
18
+ group.label.toLowerCase().includes(term) ||
19
+ (option.description ? option.description.toLowerCase().includes(term) : false))
20
+ .map((option) => ({ group, option })));
21
+ }
22
+ }