@pallasoft/ps-input-components 3.1.0 → 3.3.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 (47) hide show
  1. package/dist/components/ps-grouped-switcher.d.ts +26 -0
  2. package/dist/components/ps-grouped-switcher.js +55 -0
  3. package/dist/components/ps-rating-input.d.ts +12 -0
  4. package/dist/components/ps-rating-input.js +12 -0
  5. package/dist/constants/ps-grouped-switcher-config.d.ts +6 -0
  6. package/dist/constants/ps-grouped-switcher-config.js +9 -0
  7. package/dist/constants/ps-rating-input-config.d.ts +14 -0
  8. package/dist/constants/ps-rating-input-config.js +18 -0
  9. package/dist/constants/ps-rating-input-size.d.ts +4 -0
  10. package/dist/constants/ps-rating-input-size.js +8 -0
  11. package/dist/esm/components/ps-grouped-switcher.js +51 -0
  12. package/dist/esm/components/ps-rating-input.js +8 -0
  13. package/dist/esm/constants/ps-grouped-switcher-config.js +6 -0
  14. package/dist/esm/constants/ps-rating-input-config.js +15 -0
  15. package/dist/esm/constants/ps-rating-input-size.js +5 -0
  16. package/dist/esm/index.js +9 -0
  17. package/dist/esm/interfaces/ps-grouped-switcher-all-option.js +1 -0
  18. package/dist/esm/interfaces/ps-grouped-switcher-group.js +1 -0
  19. package/dist/esm/interfaces/ps-grouped-switcher-section.js +1 -0
  20. package/dist/esm/interfaces/ps-grouped-switcher-sections-input.js +1 -0
  21. package/dist/esm/portable-text-core/components/ps-portable-text-core-editor.js +7 -2
  22. package/dist/esm/portable-text-core/index.js +1 -0
  23. package/dist/esm/portable-text-core/interfaces/ps-portable-text-editor-handle.js +1 -0
  24. package/dist/esm/portable-text-core/services/ps-portable-text-command-service.js +20 -0
  25. package/dist/esm/services/ps-grouped-switcher-service.js +60 -0
  26. package/dist/index.d.ts +9 -0
  27. package/dist/index.js +9 -0
  28. package/dist/interfaces/ps-grouped-switcher-all-option.d.ts +4 -0
  29. package/dist/interfaces/ps-grouped-switcher-all-option.js +2 -0
  30. package/dist/interfaces/ps-grouped-switcher-group.d.ts +5 -0
  31. package/dist/interfaces/ps-grouped-switcher-group.js +2 -0
  32. package/dist/interfaces/ps-grouped-switcher-section.d.ts +5 -0
  33. package/dist/interfaces/ps-grouped-switcher-section.js +2 -0
  34. package/dist/interfaces/ps-grouped-switcher-sections-input.d.ts +11 -0
  35. package/dist/interfaces/ps-grouped-switcher-sections-input.js +2 -0
  36. package/dist/portable-text-core/components/ps-portable-text-core-editor.d.ts +1 -1
  37. package/dist/portable-text-core/components/ps-portable-text-core-editor.js +6 -1
  38. package/dist/portable-text-core/index.d.ts +1 -0
  39. package/dist/portable-text-core/index.js +1 -0
  40. package/dist/portable-text-core/interfaces/ps-portable-text-core-props.d.ts +3 -1
  41. package/dist/portable-text-core/interfaces/ps-portable-text-editor-handle.d.ts +6 -0
  42. package/dist/portable-text-core/interfaces/ps-portable-text-editor-handle.js +2 -0
  43. package/dist/portable-text-core/services/ps-portable-text-command-service.d.ts +3 -0
  44. package/dist/portable-text-core/services/ps-portable-text-command-service.js +20 -0
  45. package/dist/services/ps-grouped-switcher-service.d.ts +8 -0
  46. package/dist/services/ps-grouped-switcher-service.js +64 -0
  47. package/package.json +1 -1
@@ -0,0 +1,26 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { PsGroupedSwitcherAllOption } from '../interfaces/ps-grouped-switcher-all-option';
3
+ import type { PsGroupedSwitcherGroup } from '../interfaces/ps-grouped-switcher-group';
4
+ export interface PsGroupedSwitcherProps<T> {
5
+ allOption?: PsGroupedSwitcherAllOption;
6
+ ariaLabel: string;
7
+ className?: string;
8
+ emptyText: string;
9
+ getItemKey: (item: T) => string;
10
+ getItemSearchText: (item: T) => string;
11
+ groups: PsGroupedSwitcherGroup<T>[];
12
+ onSelect: (key: string) => void;
13
+ onTogglePin?: (key: string) => void;
14
+ pinLabel?: string;
15
+ pinnedKeys?: string[];
16
+ pinnedLabel?: string;
17
+ recentKeys?: string[];
18
+ recentLabel?: string;
19
+ renderItem: (item: T) => ReactNode;
20
+ renderSectionLabel?: (key: string, label: string) => ReactNode;
21
+ renderSelected: (item: T | null) => ReactNode;
22
+ searchPlaceholder: string;
23
+ selectedKey: string;
24
+ unpinLabel?: string;
25
+ }
26
+ export declare const PsGroupedSwitcher: <T>({ allOption, ariaLabel, className, emptyText, getItemKey, getItemSearchText, groups, onSelect, onTogglePin, pinLabel, pinnedKeys, pinnedLabel, recentKeys, recentLabel, renderItem, renderSectionLabel, renderSelected, searchPlaceholder, selectedKey, unpinLabel, }: PsGroupedSwitcherProps<T>) => import("react").JSX.Element;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsGroupedSwitcher = 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_grouped_switcher_config_1 = require("../constants/ps-grouped-switcher-config");
11
+ const ps_grouped_switcher_service_1 = require("../services/ps-grouped-switcher-service");
12
+ const PsGroupedSwitcher = ({ allOption, ariaLabel, className, emptyText, getItemKey, getItemSearchText, groups, onSelect, onTogglePin, pinLabel = 'Pin', pinnedKeys = [], pinnedLabel = 'Pinned', recentKeys = [], recentLabel = 'Recent', renderItem, renderSectionLabel, renderSelected, searchPlaceholder, selectedKey, unpinLabel = 'Unpin', }) => {
13
+ const [isOpen, setIsOpen] = (0, react_1.useState)(false);
14
+ const [query, setQuery] = (0, react_1.useState)('');
15
+ const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Dropdown);
16
+ const items = groups.flatMap((group) => group.items);
17
+ const selected = items.find((item) => getItemKey(item) === selectedKey);
18
+ const sections = ps_grouped_switcher_service_1.PsGroupedSwitcherService.sections({
19
+ getItemKey,
20
+ getItemSearchText,
21
+ groups,
22
+ pinnedKeys,
23
+ pinnedLabel,
24
+ query,
25
+ recentKeys,
26
+ recentLabel,
27
+ });
28
+ const showAll = allOption !== undefined && query.trim() === '';
29
+ const isAllSelected = selectedKey === '' || selected === undefined;
30
+ const choose = (key) => {
31
+ onSelect(key);
32
+ setIsOpen(false);
33
+ setQuery('');
34
+ };
35
+ const onAction = (id) => {
36
+ const text = String(id);
37
+ choose(text === ps_grouped_switcher_config_1.PsGroupedSwitcherConfig.allKey ? '' : ps_grouped_switcher_service_1.PsGroupedSwitcherService.itemKey(text));
38
+ };
39
+ return ((0, jsx_runtime_1.jsxs)(react_aria_components_1.DialogTrigger, { isOpen: isOpen, onOpenChange: (next) => {
40
+ setIsOpen(next);
41
+ if (!next)
42
+ setQuery('');
43
+ }, children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.Button, { "aria-label": ariaLabel, className: ps_design_system_1.PallasoftClassNameService.merge('ps-input-control ps-focus-ring relative flex w-full min-w-0 items-center pr-10 text-left text-sm font-semibold', className), children: [(0, jsx_runtime_1.jsx)("span", { className: 'min-w-0 flex-1', children: renderSelected(isAllSelected ? null : selected) }), (0, jsx_runtime_1.jsx)(ps_icons_1.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: ps_icons_1.PsIconKey.Chevron })] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Popover, { className: 'ps-floating-panel w-[max(var(--trigger-width),min(32rem,calc(100vw-2rem)))] min-w-[var(--trigger-width)] overflow-hidden p-0 outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: (0, jsx_runtime_1.jsx)(react_aria_components_1.Dialog, { "aria-label": ariaLabel, className: 'flex max-h-[min(34rem,75vh)] flex-col outline-none', children: (0, jsx_runtime_1.jsxs)(react_aria_components_1.Autocomplete, { filter: () => true, inputValue: query, onInputChange: setQuery, children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.SearchField, { "aria-label": searchPlaceholder, autoFocus: true, className: 'relative block border-b border-line p-2', children: [(0, jsx_runtime_1.jsx)(react_aria_components_1.Input, { className: 'ps-input-control ps-focus-ring w-full pl-10 text-sm font-semibold', placeholder: searchPlaceholder }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute left-5 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/45', iconKey: ps_icons_1.PsIconKey.Search })] }), (0, jsx_runtime_1.jsxs)(react_aria_components_1.ListBox, { "aria-label": ariaLabel, className: 'ps-scrollbar-hidden min-h-0 flex-1 overflow-y-auto p-1.5 outline-none', disallowEmptySelection: true, onAction: onAction, renderEmptyState: () => ((0, jsx_runtime_1.jsx)("div", { className: 'px-3 py-6 text-center text-sm text-ink/55', children: emptyText })), selectedKeys: [
44
+ isAllSelected
45
+ ? ps_grouped_switcher_config_1.PsGroupedSwitcherConfig.allKey
46
+ : ps_grouped_switcher_service_1.PsGroupedSwitcherService.selectedOptionId(sections, selectedKey, getItemKey),
47
+ ], selectionMode: 'single', children: [showAll ? ((0, jsx_runtime_1.jsx)(react_aria_components_1.ListBoxSection, { children: (0, jsx_runtime_1.jsx)(react_aria_components_1.ListBoxItem, { className: 'ps-floating-option min-h-12 text-ink', id: ps_grouped_switcher_config_1.PsGroupedSwitcherConfig.allKey, textValue: 'all', children: allOption.content }) })) : null, sections.map((section) => ((0, jsx_runtime_1.jsxs)(react_aria_components_1.ListBoxSection, { id: section.key, children: [(0, jsx_runtime_1.jsx)(react_aria_components_1.Header, { className: 'px-3 pb-1 pt-3 text-xs font-semibold tracking-wide text-ink/50 uppercase', children: renderSectionLabel === undefined
48
+ ? section.label
49
+ : renderSectionLabel(section.key, section.label) }), section.items.map((item) => {
50
+ const key = getItemKey(item);
51
+ const isPinned = pinnedKeys.includes(key);
52
+ return ((0, jsx_runtime_1.jsxs)(react_aria_components_1.ListBoxItem, { className: 'ps-floating-option group min-h-14 text-ink', id: ps_grouped_switcher_service_1.PsGroupedSwitcherService.optionId(section.key, key), textValue: getItemSearchText(item), children: [(0, jsx_runtime_1.jsx)("span", { className: 'min-w-0 flex-1', children: renderItem(item) }), onTogglePin === undefined ? null : ((0, jsx_runtime_1.jsx)(react_aria_components_1.Button, { "aria-label": isPinned ? unpinLabel : pinLabel, className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring flex h-9 w-9 shrink-0 items-center justify-center rounded-ps-control text-ink/35 transition-opacity hover:text-primary focus-visible:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100', isPinned ? 'opacity-100' : 'opacity-0'), onPress: () => onTogglePin(key), children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: ps_design_system_1.PallasoftClassNameService.merge('h-4 w-4', isPinned ? 'text-primary' : ''), iconKey: ps_icons_1.PsIconKey.Star }) }))] }, key));
53
+ })] }, section.key)))] })] }) }) })] }));
54
+ };
55
+ exports.PsGroupedSwitcher = PsGroupedSwitcher;
@@ -0,0 +1,12 @@
1
+ import { PsRatingInputSize } from '../constants/ps-rating-input-size';
2
+ export interface PsRatingInputProps {
3
+ isDisabled?: boolean;
4
+ label: string;
5
+ max?: number;
6
+ name?: string;
7
+ onChange: (score: number) => void;
8
+ optionLabel: (score: number) => string;
9
+ size?: PsRatingInputSize;
10
+ value: number;
11
+ }
12
+ export declare const PsRatingInput: ({ isDisabled, label, max, name, onChange, optionLabel, size, value, }: PsRatingInputProps) => import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsRatingInput = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
6
+ const ps_icons_1 = require("@pallasoft/ps-icons");
7
+ const ps_rating_input_config_1 = require("../constants/ps-rating-input-config");
8
+ const ps_rating_input_size_1 = require("../constants/ps-rating-input-size");
9
+ const PsRatingInput = ({ isDisabled = false, label, max = ps_rating_input_config_1.PsRatingInputConfig.defaultMax, name, onChange, optionLabel, size = ps_rating_input_size_1.PsRatingInputSize.Medium, value, }) => ((0, jsx_runtime_1.jsxs)("fieldset", { className: 'm-0 min-w-0 border-0 p-0', disabled: isDisabled, children: [(0, jsx_runtime_1.jsx)("legend", { className: 'sr-only', children: label }), (0, jsx_runtime_1.jsx)("div", { className: '-ml-1.5 flex items-center', children: Array.from({ length: max }, (_, index) => index + 1).map((score) => ((0, jsx_runtime_1.jsxs)("label", { className: ps_design_system_1.PallasoftClassNameService.merge('relative flex cursor-pointer items-center justify-center', ps_rating_input_config_1.PsRatingInputConfig.targetClassNames[size], isDisabled && 'cursor-not-allowed opacity-60'), title: optionLabel(score), children: [(0, jsx_runtime_1.jsx)("input", { "aria-label": optionLabel(score), checked: score === value, className: 'peer sr-only', name: name, onChange: () => onChange(score), type: 'radio', value: score }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { className: ps_design_system_1.PallasoftClassNameService.merge('transition-colors', ps_rating_input_config_1.PsRatingInputConfig.iconClassNames[size], score <= value
10
+ ? ps_rating_input_config_1.PsRatingInputConfig.filledClassName
11
+ : ps_rating_input_config_1.PsRatingInputConfig.emptyClassName, ps_rating_input_config_1.PsRatingInputConfig.focusClassName), iconKey: ps_icons_1.PsIconKey.Star })] }, score))) })] }));
12
+ exports.PsRatingInput = PsRatingInput;
@@ -0,0 +1,6 @@
1
+ export declare const PsGroupedSwitcherConfig: {
2
+ readonly allKey: "all";
3
+ readonly maxRecent: 3;
4
+ readonly pinnedSection: "pinned";
5
+ readonly recentSection: "recent";
6
+ };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsGroupedSwitcherConfig = void 0;
4
+ exports.PsGroupedSwitcherConfig = {
5
+ allKey: 'all',
6
+ maxRecent: 3,
7
+ pinnedSection: 'pinned',
8
+ recentSection: 'recent',
9
+ };
@@ -0,0 +1,14 @@
1
+ export declare const PsRatingInputConfig: {
2
+ readonly defaultMax: 5;
3
+ readonly filledClassName: "fill-current text-primary";
4
+ readonly emptyClassName: "text-ink/30 hover:text-primary dark:text-white/30";
5
+ readonly focusClassName: "peer-focus-visible:rounded-ps-control peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-primary";
6
+ readonly iconClassNames: {
7
+ readonly large: "h-10 w-10";
8
+ readonly medium: "h-8 w-8";
9
+ };
10
+ readonly targetClassNames: {
11
+ readonly large: "h-14 w-14";
12
+ readonly medium: "h-12 w-12";
13
+ };
14
+ };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsRatingInputConfig = void 0;
4
+ const ps_rating_input_size_1 = require("./ps-rating-input-size");
5
+ exports.PsRatingInputConfig = {
6
+ defaultMax: 5,
7
+ filledClassName: 'fill-current text-primary',
8
+ emptyClassName: 'text-ink/30 hover:text-primary dark:text-white/30',
9
+ focusClassName: 'peer-focus-visible:rounded-ps-control peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-primary',
10
+ iconClassNames: {
11
+ [ps_rating_input_size_1.PsRatingInputSize.Large]: 'h-10 w-10',
12
+ [ps_rating_input_size_1.PsRatingInputSize.Medium]: 'h-8 w-8',
13
+ },
14
+ targetClassNames: {
15
+ [ps_rating_input_size_1.PsRatingInputSize.Large]: 'h-14 w-14',
16
+ [ps_rating_input_size_1.PsRatingInputSize.Medium]: 'h-12 w-12',
17
+ },
18
+ };
@@ -0,0 +1,4 @@
1
+ export declare enum PsRatingInputSize {
2
+ Large = "large",
3
+ Medium = "medium"
4
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsRatingInputSize = void 0;
4
+ var PsRatingInputSize;
5
+ (function (PsRatingInputSize) {
6
+ PsRatingInputSize["Large"] = "large";
7
+ PsRatingInputSize["Medium"] = "medium";
8
+ })(PsRatingInputSize || (exports.PsRatingInputSize = PsRatingInputSize = {}));
@@ -0,0 +1,51 @@
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 { Autocomplete, Button, Dialog, DialogTrigger, Header, Input, ListBox, ListBoxItem, ListBoxSection, Popover, SearchField, } from 'react-aria-components';
7
+ import { PsGroupedSwitcherConfig } from '../constants/ps-grouped-switcher-config';
8
+ import { PsGroupedSwitcherService } from '../services/ps-grouped-switcher-service';
9
+ export const PsGroupedSwitcher = ({ allOption, ariaLabel, className, emptyText, getItemKey, getItemSearchText, groups, onSelect, onTogglePin, pinLabel = 'Pin', pinnedKeys = [], pinnedLabel = 'Pinned', recentKeys = [], recentLabel = 'Recent', renderItem, renderSectionLabel, renderSelected, searchPlaceholder, selectedKey, unpinLabel = 'Unpin', }) => {
10
+ const [isOpen, setIsOpen] = useState(false);
11
+ const [query, setQuery] = useState('');
12
+ const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
13
+ const items = groups.flatMap((group) => group.items);
14
+ const selected = items.find((item) => getItemKey(item) === selectedKey);
15
+ const sections = PsGroupedSwitcherService.sections({
16
+ getItemKey,
17
+ getItemSearchText,
18
+ groups,
19
+ pinnedKeys,
20
+ pinnedLabel,
21
+ query,
22
+ recentKeys,
23
+ recentLabel,
24
+ });
25
+ const showAll = allOption !== undefined && query.trim() === '';
26
+ const isAllSelected = selectedKey === '' || selected === undefined;
27
+ const choose = (key) => {
28
+ onSelect(key);
29
+ setIsOpen(false);
30
+ setQuery('');
31
+ };
32
+ const onAction = (id) => {
33
+ const text = String(id);
34
+ choose(text === PsGroupedSwitcherConfig.allKey ? '' : PsGroupedSwitcherService.itemKey(text));
35
+ };
36
+ return (_jsxs(DialogTrigger, { isOpen: isOpen, onOpenChange: (next) => {
37
+ setIsOpen(next);
38
+ if (!next)
39
+ setQuery('');
40
+ }, children: [_jsxs(Button, { "aria-label": ariaLabel, className: PallasoftClassNameService.merge('ps-input-control ps-focus-ring relative flex w-full min-w-0 items-center pr-10 text-left text-sm font-semibold', className), children: [_jsx("span", { className: 'min-w-0 flex-1', children: renderSelected(isAllSelected ? null : 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 })] }), _jsx(Popover, { className: 'ps-floating-panel w-[max(var(--trigger-width),min(32rem,calc(100vw-2rem)))] min-w-[var(--trigger-width)] overflow-hidden p-0 outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(Dialog, { "aria-label": ariaLabel, className: 'flex max-h-[min(34rem,75vh)] flex-col outline-none', children: _jsxs(Autocomplete, { filter: () => true, inputValue: query, onInputChange: setQuery, children: [_jsxs(SearchField, { "aria-label": searchPlaceholder, autoFocus: true, className: 'relative block border-b border-line p-2', children: [_jsx(Input, { className: 'ps-input-control ps-focus-ring w-full pl-10 text-sm font-semibold', placeholder: searchPlaceholder }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute left-5 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/45', iconKey: PsIconKey.Search })] }), _jsxs(ListBox, { "aria-label": ariaLabel, className: 'ps-scrollbar-hidden min-h-0 flex-1 overflow-y-auto p-1.5 outline-none', disallowEmptySelection: true, onAction: onAction, renderEmptyState: () => (_jsx("div", { className: 'px-3 py-6 text-center text-sm text-ink/55', children: emptyText })), selectedKeys: [
41
+ isAllSelected
42
+ ? PsGroupedSwitcherConfig.allKey
43
+ : PsGroupedSwitcherService.selectedOptionId(sections, selectedKey, getItemKey),
44
+ ], selectionMode: 'single', children: [showAll ? (_jsx(ListBoxSection, { children: _jsx(ListBoxItem, { className: 'ps-floating-option min-h-12 text-ink', id: PsGroupedSwitcherConfig.allKey, textValue: 'all', children: allOption.content }) })) : null, sections.map((section) => (_jsxs(ListBoxSection, { id: section.key, children: [_jsx(Header, { className: 'px-3 pb-1 pt-3 text-xs font-semibold tracking-wide text-ink/50 uppercase', children: renderSectionLabel === undefined
45
+ ? section.label
46
+ : renderSectionLabel(section.key, section.label) }), section.items.map((item) => {
47
+ const key = getItemKey(item);
48
+ const isPinned = pinnedKeys.includes(key);
49
+ return (_jsxs(ListBoxItem, { className: 'ps-floating-option group min-h-14 text-ink', id: PsGroupedSwitcherService.optionId(section.key, key), textValue: getItemSearchText(item), children: [_jsx("span", { className: 'min-w-0 flex-1', children: renderItem(item) }), onTogglePin === undefined ? null : (_jsx(Button, { "aria-label": isPinned ? unpinLabel : pinLabel, className: PallasoftClassNameService.merge('ps-focus-ring flex h-9 w-9 shrink-0 items-center justify-center rounded-ps-control text-ink/35 transition-opacity hover:text-primary focus-visible:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100', isPinned ? 'opacity-100' : 'opacity-0'), onPress: () => onTogglePin(key), children: _jsx(PsIcon, { "aria-hidden": 'true', className: PallasoftClassNameService.merge('h-4 w-4', isPinned ? 'text-primary' : ''), iconKey: PsIconKey.Star }) }))] }, key));
50
+ })] }, section.key)))] })] }) }) })] }));
51
+ };
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
+ import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
4
+ import { PsRatingInputConfig } from '../constants/ps-rating-input-config';
5
+ import { PsRatingInputSize } from '../constants/ps-rating-input-size';
6
+ export const PsRatingInput = ({ isDisabled = false, label, max = PsRatingInputConfig.defaultMax, name, onChange, optionLabel, size = PsRatingInputSize.Medium, value, }) => (_jsxs("fieldset", { className: 'm-0 min-w-0 border-0 p-0', disabled: isDisabled, children: [_jsx("legend", { className: 'sr-only', children: label }), _jsx("div", { className: '-ml-1.5 flex items-center', children: Array.from({ length: max }, (_, index) => index + 1).map((score) => (_jsxs("label", { className: PallasoftClassNameService.merge('relative flex cursor-pointer items-center justify-center', PsRatingInputConfig.targetClassNames[size], isDisabled && 'cursor-not-allowed opacity-60'), title: optionLabel(score), children: [_jsx("input", { "aria-label": optionLabel(score), checked: score === value, className: 'peer sr-only', name: name, onChange: () => onChange(score), type: 'radio', value: score }), _jsx(PsIcon, { className: PallasoftClassNameService.merge('transition-colors', PsRatingInputConfig.iconClassNames[size], score <= value
7
+ ? PsRatingInputConfig.filledClassName
8
+ : PsRatingInputConfig.emptyClassName, PsRatingInputConfig.focusClassName), iconKey: PsIconKey.Star })] }, score))) })] }));
@@ -0,0 +1,6 @@
1
+ export const PsGroupedSwitcherConfig = {
2
+ allKey: 'all',
3
+ maxRecent: 3,
4
+ pinnedSection: 'pinned',
5
+ recentSection: 'recent',
6
+ };
@@ -0,0 +1,15 @@
1
+ import { PsRatingInputSize } from './ps-rating-input-size';
2
+ export const PsRatingInputConfig = {
3
+ defaultMax: 5,
4
+ filledClassName: 'fill-current text-primary',
5
+ emptyClassName: 'text-ink/30 hover:text-primary dark:text-white/30',
6
+ focusClassName: 'peer-focus-visible:rounded-ps-control peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-primary',
7
+ iconClassNames: {
8
+ [PsRatingInputSize.Large]: 'h-10 w-10',
9
+ [PsRatingInputSize.Medium]: 'h-8 w-8',
10
+ },
11
+ targetClassNames: {
12
+ [PsRatingInputSize.Large]: 'h-14 w-14',
13
+ [PsRatingInputSize.Medium]: 'h-12 w-12',
14
+ },
15
+ };
@@ -0,0 +1,5 @@
1
+ export var PsRatingInputSize;
2
+ (function (PsRatingInputSize) {
3
+ PsRatingInputSize["Large"] = "large";
4
+ PsRatingInputSize["Medium"] = "medium";
5
+ })(PsRatingInputSize || (PsRatingInputSize = {}));
package/dist/esm/index.js CHANGED
@@ -54,3 +54,12 @@ export * from './interfaces/ps-checkbox-matrix-group';
54
54
  export * from './interfaces/ps-checkbox-matrix-labels';
55
55
  export * from './interfaces/ps-checkbox-matrix-row';
56
56
  export * from './services/ps-checkbox-matrix-service';
57
+ export * from './components/ps-rating-input';
58
+ export * from './constants/ps-rating-input-size';
59
+ export * from './components/ps-grouped-switcher';
60
+ export * from './constants/ps-grouped-switcher-config';
61
+ export * from './interfaces/ps-grouped-switcher-all-option';
62
+ export * from './interfaces/ps-grouped-switcher-group';
63
+ export * from './interfaces/ps-grouped-switcher-section';
64
+ export * from './interfaces/ps-grouped-switcher-sections-input';
65
+ export * from './services/ps-grouped-switcher-service';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from 'react';
2
+ import { useEffect, useImperativeHandle, useRef, useState } from 'react';
3
3
  import { EditorContent, useEditor, useEditorState } from '@tiptap/react';
4
4
  import { PsPortableTextCoreContext } from '../contexts/ps-portable-text-core-context';
5
5
  import { PsPortableTextCommandService } from '../services/ps-portable-text-command-service';
6
6
  import { PsPortableTextConverterService } from '../services/ps-portable-text-converter-service';
7
7
  import { PsPortableTextEditorService } from '../services/ps-portable-text-editor-service';
8
8
  import { PsPortableTextExtensionService } from '../services/ps-portable-text-extension-service';
9
- export const PsPortableTextCoreEditor = ({ className, classNames, disabled = false, editableClassName = '', label, minRows = 4, onChange, placeholder = '', toolbar, value, }) => {
9
+ export const PsPortableTextCoreEditor = ({ className, classNames, disabled = false, handleRef, editableClassName = '', label, minRows = 4, onChange, placeholder = '', toolbar, value, }) => {
10
10
  const [isLinkOpen, setIsLinkOpen] = useState(false);
11
11
  const lastValue = useRef(value);
12
12
  const editor = useEditor({
@@ -32,6 +32,11 @@ export const PsPortableTextCoreEditor = ({ className, classNames, disabled = fal
32
32
  editor,
33
33
  selector: ({ editor: current }) => PsPortableTextCommandService.state(current),
34
34
  });
35
+ useImperativeHandle(handleRef, () => ({
36
+ focus: () => PsPortableTextCommandService.focus(editor),
37
+ insertBlocks: (blocks) => PsPortableTextCommandService.insertBlocks(editor, blocks),
38
+ isCurrentBlockEmpty: () => PsPortableTextCommandService.isCurrentBlockEmpty(editor),
39
+ }), [editor]);
35
40
  useEffect(() => {
36
41
  editor.setEditable(!disabled, false);
37
42
  }, [editor, disabled]);
@@ -5,6 +5,7 @@ export * from './hooks/use-ps-portable-text-toolbar';
5
5
  export * from './interfaces/ps-portable-text-active-formats';
6
6
  export * from './interfaces/ps-portable-text-core-class-names';
7
7
  export * from './interfaces/ps-portable-text-core-props';
8
+ export * from './interfaces/ps-portable-text-editor-handle';
8
9
  export * from './interfaces/ps-portable-text-editor-labels';
9
10
  export * from './services/ps-portable-text-converter-service';
10
11
  export * from './services/ps-portable-text-editor-service';
@@ -1,4 +1,5 @@
1
1
  import { PsPortableTextCoreConfig } from '../constants/ps-portable-text-core-config';
2
+ import { PsPortableTextConverterService } from './ps-portable-text-converter-service';
2
3
  import { PsPortableTextEditorService } from './ps-portable-text-editor-service';
3
4
  export class PsPortableTextCommandService {
4
5
  static state(editor) {
@@ -70,4 +71,23 @@ export class PsPortableTextCommandService {
70
71
  if (editor !== null)
71
72
  editor.commands.focus();
72
73
  }
74
+ static insertBlocks(editor, blocks) {
75
+ if (editor === null || blocks.length === 0)
76
+ return;
77
+ const content = PsPortableTextConverterService.toDocument(blocks).content;
78
+ if (content === undefined)
79
+ return;
80
+ const [first] = content;
81
+ const isOneLine = content.length === 1 &&
82
+ first.type === PsPortableTextCoreConfig.nodes.paragraph &&
83
+ first.content !== undefined;
84
+ editor
85
+ .chain()
86
+ .focus()
87
+ .insertContent(isOneLine && first.content !== undefined ? first.content : content)
88
+ .run();
89
+ }
90
+ static isCurrentBlockEmpty(editor) {
91
+ return editor === null ? true : editor.state.selection.$from.parent.content.size === 0;
92
+ }
73
93
  }
@@ -0,0 +1,60 @@
1
+ import { PsGroupedSwitcherConfig } from '../constants/ps-grouped-switcher-config';
2
+ export class PsGroupedSwitcherService {
3
+ static optionId(sectionKey, itemKey) {
4
+ return `${sectionKey}:${itemKey}`;
5
+ }
6
+ static selectedOptionId(sections, selectedKey, getItemKey) {
7
+ const section = sections.find((candidate) => candidate.items.some((item) => getItemKey(item) === selectedKey));
8
+ return section === undefined ? '' : PsGroupedSwitcherService.optionId(section.key, selectedKey);
9
+ }
10
+ static itemKey(optionId) {
11
+ return optionId.slice(optionId.indexOf(':') + 1);
12
+ }
13
+ static sections(input) {
14
+ const needle = input.query.trim().toLowerCase();
15
+ const words = needle.split(/\s+/).filter(Boolean);
16
+ const all = input.groups.flatMap((group) => group.items);
17
+ const byKey = new Map(all.map((item) => [input.getItemKey(item), item]));
18
+ const pick = (keys) => keys.flatMap((key) => {
19
+ const item = byKey.get(key);
20
+ return item === undefined ? [] : [item];
21
+ });
22
+ const pinned = pick(input.pinnedKeys);
23
+ const recent = pick(input.recentKeys.filter((key) => !input.pinnedKeys.includes(key))).slice(0, PsGroupedSwitcherConfig.maxRecent);
24
+ const groups = input.groups
25
+ .map((group) => ({
26
+ items: needle === ''
27
+ ? group.items
28
+ : group.items.filter((item) => {
29
+ const haystack = `${group.label} ${input.getItemSearchText(item)}`.toLowerCase();
30
+ return words.every((word) => haystack.includes(word));
31
+ }),
32
+ key: group.key,
33
+ label: group.label,
34
+ }))
35
+ .filter((group) => group.items.length > 0);
36
+ if (needle !== '')
37
+ return groups;
38
+ return [
39
+ ...(pinned.length > 0
40
+ ? [
41
+ {
42
+ items: pinned,
43
+ key: PsGroupedSwitcherConfig.pinnedSection,
44
+ label: input.pinnedLabel,
45
+ },
46
+ ]
47
+ : []),
48
+ ...(recent.length > 0
49
+ ? [
50
+ {
51
+ items: recent,
52
+ key: PsGroupedSwitcherConfig.recentSection,
53
+ label: input.recentLabel,
54
+ },
55
+ ]
56
+ : []),
57
+ ...groups,
58
+ ];
59
+ }
60
+ }
package/dist/index.d.ts CHANGED
@@ -54,3 +54,12 @@ export * from './interfaces/ps-checkbox-matrix-group';
54
54
  export * from './interfaces/ps-checkbox-matrix-labels';
55
55
  export * from './interfaces/ps-checkbox-matrix-row';
56
56
  export * from './services/ps-checkbox-matrix-service';
57
+ export * from './components/ps-rating-input';
58
+ export * from './constants/ps-rating-input-size';
59
+ export * from './components/ps-grouped-switcher';
60
+ export * from './constants/ps-grouped-switcher-config';
61
+ export * from './interfaces/ps-grouped-switcher-all-option';
62
+ export * from './interfaces/ps-grouped-switcher-group';
63
+ export * from './interfaces/ps-grouped-switcher-section';
64
+ export * from './interfaces/ps-grouped-switcher-sections-input';
65
+ export * from './services/ps-grouped-switcher-service';
package/dist/index.js CHANGED
@@ -70,3 +70,12 @@ __exportStar(require("./interfaces/ps-checkbox-matrix-group"), exports);
70
70
  __exportStar(require("./interfaces/ps-checkbox-matrix-labels"), exports);
71
71
  __exportStar(require("./interfaces/ps-checkbox-matrix-row"), exports);
72
72
  __exportStar(require("./services/ps-checkbox-matrix-service"), exports);
73
+ __exportStar(require("./components/ps-rating-input"), exports);
74
+ __exportStar(require("./constants/ps-rating-input-size"), exports);
75
+ __exportStar(require("./components/ps-grouped-switcher"), exports);
76
+ __exportStar(require("./constants/ps-grouped-switcher-config"), exports);
77
+ __exportStar(require("./interfaces/ps-grouped-switcher-all-option"), exports);
78
+ __exportStar(require("./interfaces/ps-grouped-switcher-group"), exports);
79
+ __exportStar(require("./interfaces/ps-grouped-switcher-section"), exports);
80
+ __exportStar(require("./interfaces/ps-grouped-switcher-sections-input"), exports);
81
+ __exportStar(require("./services/ps-grouped-switcher-service"), exports);
@@ -0,0 +1,4 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface PsGroupedSwitcherAllOption {
3
+ content: ReactNode;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export interface PsGroupedSwitcherGroup<T> {
2
+ items: T[];
3
+ key: string;
4
+ label: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export interface PsGroupedSwitcherSection<T> {
2
+ items: T[];
3
+ key: string;
4
+ label: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ import type { PsGroupedSwitcherGroup } from './ps-grouped-switcher-group';
2
+ export interface PsGroupedSwitcherSectionsInput<T> {
3
+ getItemKey: (item: T) => string;
4
+ getItemSearchText: (item: T) => string;
5
+ groups: PsGroupedSwitcherGroup<T>[];
6
+ pinnedKeys: string[];
7
+ pinnedLabel: string;
8
+ query: string;
9
+ recentKeys: string[];
10
+ recentLabel: string;
11
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,2 +1,2 @@
1
1
  import type { PsPortableTextCoreProps } from '../interfaces/ps-portable-text-core-props';
2
- export declare const PsPortableTextCoreEditor: ({ className, classNames, disabled, editableClassName, label, minRows, onChange, placeholder, toolbar, value, }: PsPortableTextCoreProps) => import("react").JSX.Element;
2
+ export declare const PsPortableTextCoreEditor: ({ className, classNames, disabled, handleRef, editableClassName, label, minRows, onChange, placeholder, toolbar, value, }: PsPortableTextCoreProps) => import("react").JSX.Element;
@@ -9,7 +9,7 @@ const ps_portable_text_command_service_1 = require("../services/ps-portable-text
9
9
  const ps_portable_text_converter_service_1 = require("../services/ps-portable-text-converter-service");
10
10
  const ps_portable_text_editor_service_1 = require("../services/ps-portable-text-editor-service");
11
11
  const ps_portable_text_extension_service_1 = require("../services/ps-portable-text-extension-service");
12
- const PsPortableTextCoreEditor = ({ className, classNames, disabled = false, editableClassName = '', label, minRows = 4, onChange, placeholder = '', toolbar, value, }) => {
12
+ const PsPortableTextCoreEditor = ({ className, classNames, disabled = false, handleRef, editableClassName = '', label, minRows = 4, onChange, placeholder = '', toolbar, value, }) => {
13
13
  const [isLinkOpen, setIsLinkOpen] = (0, react_1.useState)(false);
14
14
  const lastValue = (0, react_1.useRef)(value);
15
15
  const editor = (0, react_2.useEditor)({
@@ -35,6 +35,11 @@ const PsPortableTextCoreEditor = ({ className, classNames, disabled = false, edi
35
35
  editor,
36
36
  selector: ({ editor: current }) => ps_portable_text_command_service_1.PsPortableTextCommandService.state(current),
37
37
  });
38
+ (0, react_1.useImperativeHandle)(handleRef, () => ({
39
+ focus: () => ps_portable_text_command_service_1.PsPortableTextCommandService.focus(editor),
40
+ insertBlocks: (blocks) => ps_portable_text_command_service_1.PsPortableTextCommandService.insertBlocks(editor, blocks),
41
+ isCurrentBlockEmpty: () => ps_portable_text_command_service_1.PsPortableTextCommandService.isCurrentBlockEmpty(editor),
42
+ }), [editor]);
38
43
  (0, react_1.useEffect)(() => {
39
44
  editor.setEditable(!disabled, false);
40
45
  }, [editor, disabled]);
@@ -5,6 +5,7 @@ export * from './hooks/use-ps-portable-text-toolbar';
5
5
  export * from './interfaces/ps-portable-text-active-formats';
6
6
  export * from './interfaces/ps-portable-text-core-class-names';
7
7
  export * from './interfaces/ps-portable-text-core-props';
8
+ export * from './interfaces/ps-portable-text-editor-handle';
8
9
  export * from './interfaces/ps-portable-text-editor-labels';
9
10
  export * from './services/ps-portable-text-converter-service';
10
11
  export * from './services/ps-portable-text-editor-service';
@@ -21,6 +21,7 @@ __exportStar(require("./hooks/use-ps-portable-text-toolbar"), exports);
21
21
  __exportStar(require("./interfaces/ps-portable-text-active-formats"), exports);
22
22
  __exportStar(require("./interfaces/ps-portable-text-core-class-names"), exports);
23
23
  __exportStar(require("./interfaces/ps-portable-text-core-props"), exports);
24
+ __exportStar(require("./interfaces/ps-portable-text-editor-handle"), exports);
24
25
  __exportStar(require("./interfaces/ps-portable-text-editor-labels"), exports);
25
26
  __exportStar(require("./services/ps-portable-text-converter-service"), exports);
26
27
  __exportStar(require("./services/ps-portable-text-editor-service"), exports);
@@ -1,12 +1,14 @@
1
- import type { ReactNode } from 'react';
1
+ import type { ReactNode, Ref } from 'react';
2
2
  import type { PortableTextBlock } from '@portabletext/schema';
3
3
  import type { PsPortableTextCoreClassNames } from './ps-portable-text-core-class-names';
4
+ import type { PsPortableTextEditorHandle } from './ps-portable-text-editor-handle';
4
5
  export interface PsPortableTextCoreProps {
5
6
  className?: string;
6
7
  classNames: PsPortableTextCoreClassNames;
7
8
  disabled?: boolean;
8
9
  editableClassName?: string;
9
10
  fallback?: ReactNode;
11
+ handleRef?: Ref<PsPortableTextEditorHandle>;
10
12
  label?: string;
11
13
  minRows?: number;
12
14
  onChange: (value: PortableTextBlock[]) => void;
@@ -0,0 +1,6 @@
1
+ import type { PortableTextBlock } from '@portabletext/schema';
2
+ export interface PsPortableTextEditorHandle {
3
+ focus: () => void;
4
+ insertBlocks: (blocks: PortableTextBlock[]) => void;
5
+ isCurrentBlockEmpty: () => boolean;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +1,4 @@
1
+ import type { PortableTextBlock } from '@portabletext/schema';
1
2
  import type { Editor } from '@tiptap/core';
2
3
  import type { PsPortableTextActiveFormats } from '../interfaces/ps-portable-text-active-formats';
3
4
  import type { PsPortableTextToolbarState } from '../interfaces/ps-portable-text-toolbar-state';
@@ -11,4 +12,6 @@ export declare class PsPortableTextCommandService {
11
12
  static setLink(editor: Editor | null, href: string): void;
12
13
  static removeLink(editor: Editor | null): void;
13
14
  static focus(editor: Editor | null): void;
15
+ static insertBlocks(editor: Editor | null, blocks: PortableTextBlock[]): void;
16
+ static isCurrentBlockEmpty(editor: Editor | null): boolean;
14
17
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsPortableTextCommandService = void 0;
4
4
  const ps_portable_text_core_config_1 = require("../constants/ps-portable-text-core-config");
5
+ const ps_portable_text_converter_service_1 = require("./ps-portable-text-converter-service");
5
6
  const ps_portable_text_editor_service_1 = require("./ps-portable-text-editor-service");
6
7
  class PsPortableTextCommandService {
7
8
  static state(editor) {
@@ -73,5 +74,24 @@ class PsPortableTextCommandService {
73
74
  if (editor !== null)
74
75
  editor.commands.focus();
75
76
  }
77
+ static insertBlocks(editor, blocks) {
78
+ if (editor === null || blocks.length === 0)
79
+ return;
80
+ const content = ps_portable_text_converter_service_1.PsPortableTextConverterService.toDocument(blocks).content;
81
+ if (content === undefined)
82
+ return;
83
+ const [first] = content;
84
+ const isOneLine = content.length === 1 &&
85
+ first.type === ps_portable_text_core_config_1.PsPortableTextCoreConfig.nodes.paragraph &&
86
+ first.content !== undefined;
87
+ editor
88
+ .chain()
89
+ .focus()
90
+ .insertContent(isOneLine && first.content !== undefined ? first.content : content)
91
+ .run();
92
+ }
93
+ static isCurrentBlockEmpty(editor) {
94
+ return editor === null ? true : editor.state.selection.$from.parent.content.size === 0;
95
+ }
76
96
  }
77
97
  exports.PsPortableTextCommandService = PsPortableTextCommandService;
@@ -0,0 +1,8 @@
1
+ import type { PsGroupedSwitcherSection } from '../interfaces/ps-grouped-switcher-section';
2
+ import type { PsGroupedSwitcherSectionsInput } from '../interfaces/ps-grouped-switcher-sections-input';
3
+ export declare class PsGroupedSwitcherService {
4
+ static optionId(sectionKey: string, itemKey: string): string;
5
+ static selectedOptionId<T>(sections: PsGroupedSwitcherSection<T>[], selectedKey: string, getItemKey: (item: T) => string): string;
6
+ static itemKey(optionId: string): string;
7
+ static sections<T>(input: PsGroupedSwitcherSectionsInput<T>): PsGroupedSwitcherSection<T>[];
8
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsGroupedSwitcherService = void 0;
4
+ const ps_grouped_switcher_config_1 = require("../constants/ps-grouped-switcher-config");
5
+ class PsGroupedSwitcherService {
6
+ static optionId(sectionKey, itemKey) {
7
+ return `${sectionKey}:${itemKey}`;
8
+ }
9
+ static selectedOptionId(sections, selectedKey, getItemKey) {
10
+ const section = sections.find((candidate) => candidate.items.some((item) => getItemKey(item) === selectedKey));
11
+ return section === undefined ? '' : PsGroupedSwitcherService.optionId(section.key, selectedKey);
12
+ }
13
+ static itemKey(optionId) {
14
+ return optionId.slice(optionId.indexOf(':') + 1);
15
+ }
16
+ static sections(input) {
17
+ const needle = input.query.trim().toLowerCase();
18
+ const words = needle.split(/\s+/).filter(Boolean);
19
+ const all = input.groups.flatMap((group) => group.items);
20
+ const byKey = new Map(all.map((item) => [input.getItemKey(item), item]));
21
+ const pick = (keys) => keys.flatMap((key) => {
22
+ const item = byKey.get(key);
23
+ return item === undefined ? [] : [item];
24
+ });
25
+ const pinned = pick(input.pinnedKeys);
26
+ const recent = pick(input.recentKeys.filter((key) => !input.pinnedKeys.includes(key))).slice(0, ps_grouped_switcher_config_1.PsGroupedSwitcherConfig.maxRecent);
27
+ const groups = input.groups
28
+ .map((group) => ({
29
+ items: needle === ''
30
+ ? group.items
31
+ : group.items.filter((item) => {
32
+ const haystack = `${group.label} ${input.getItemSearchText(item)}`.toLowerCase();
33
+ return words.every((word) => haystack.includes(word));
34
+ }),
35
+ key: group.key,
36
+ label: group.label,
37
+ }))
38
+ .filter((group) => group.items.length > 0);
39
+ if (needle !== '')
40
+ return groups;
41
+ return [
42
+ ...(pinned.length > 0
43
+ ? [
44
+ {
45
+ items: pinned,
46
+ key: ps_grouped_switcher_config_1.PsGroupedSwitcherConfig.pinnedSection,
47
+ label: input.pinnedLabel,
48
+ },
49
+ ]
50
+ : []),
51
+ ...(recent.length > 0
52
+ ? [
53
+ {
54
+ items: recent,
55
+ key: ps_grouped_switcher_config_1.PsGroupedSwitcherConfig.recentSection,
56
+ label: input.recentLabel,
57
+ },
58
+ ]
59
+ : []),
60
+ ...groups,
61
+ ];
62
+ }
63
+ }
64
+ exports.PsGroupedSwitcherService = PsGroupedSwitcherService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-input-components",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "private": false,
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",