colibri-ui 0.8.93 → 0.8.95

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 (44) hide show
  1. package/Modal/Modal.module.css +1 -0
  2. package/Modal/index.js +5 -7
  3. package/Pagination/Pagination.d.ts +3 -0
  4. package/Pagination/Pagination.js +162 -0
  5. package/Pagination/Pagination.module.css +118 -0
  6. package/Pagination/helpers.d.ts +5 -0
  7. package/Pagination/helpers.js +36 -0
  8. package/Pagination/index.d.ts +3 -0
  9. package/Pagination/index.js +9 -0
  10. package/Pagination/index.props.d.ts +31 -0
  11. package/Pagination/package.json +9 -0
  12. package/Theme/index.d.ts +10 -0
  13. package/Theme/themes/buenos_aires.d.ts +5 -0
  14. package/Theme/themes/buenos_aires.js +5 -0
  15. package/Theme/themes/jaipur.d.ts +5 -0
  16. package/Theme/themes/jaipur.js +5 -0
  17. package/esm/Modal/Modal.module.css +1 -0
  18. package/esm/Modal/index.js +6 -8
  19. package/esm/Pagination/Pagination.d.ts +3 -0
  20. package/esm/Pagination/Pagination.js +154 -0
  21. package/esm/Pagination/Pagination.module.css +118 -0
  22. package/esm/Pagination/helpers.d.ts +5 -0
  23. package/esm/Pagination/helpers.js +31 -0
  24. package/esm/Pagination/index.d.ts +3 -0
  25. package/esm/Pagination/index.js +2 -0
  26. package/esm/Pagination/index.props.d.ts +31 -0
  27. package/esm/Theme/index.d.ts +10 -0
  28. package/esm/Theme/themes/buenos_aires.d.ts +5 -0
  29. package/esm/Theme/themes/buenos_aires.js +5 -0
  30. package/esm/Theme/themes/jaipur.d.ts +5 -0
  31. package/esm/Theme/themes/jaipur.js +5 -0
  32. package/esm/hooks/useTrapFocus/index.d.ts +7 -2
  33. package/esm/hooks/useTrapFocus/index.js +62 -7
  34. package/esm/index.d.ts +2 -0
  35. package/esm/index.js +2 -0
  36. package/esm/theme_ba_variables.css +5 -0
  37. package/esm/theme_jaipur_variables.css +5 -0
  38. package/hooks/useTrapFocus/index.d.ts +7 -2
  39. package/hooks/useTrapFocus/index.js +61 -6
  40. package/index.d.ts +2 -0
  41. package/index.js +5 -0
  42. package/package.json +1 -1
  43. package/theme_ba_variables.css +5 -0
  44. package/theme_jaipur_variables.css +5 -0
@@ -0,0 +1,154 @@
1
+ import { __rest } from 'tslib';
2
+ import React, { useState, useMemo } from 'react';
3
+ import clsx from 'clsx';
4
+ import { Chevron } from '../Icons/Chevron.js';
5
+ import { Input } from '../Input/index.js';
6
+ import { Select } from '../Select/index.js';
7
+ import { clamp, getPaginationItems, findEnabledPage, toTotalPages } from './helpers.js';
8
+ import styles from './Pagination.module.css';
9
+
10
+ const NO_PAGES = [];
11
+ const DEFAULT_PAGE_SIZE_OPTIONS = [20, 50, 100];
12
+ const defaultPageLabel = page => `Page ${page}`;
13
+ const Pagination = _a => {
14
+ var {
15
+ totalPages,
16
+ page,
17
+ defaultPage = 1,
18
+ onPageChange,
19
+ siblingCount = 1,
20
+ boundaryCount = 1,
21
+ disabledPages = NO_PAGES,
22
+ disabled = false,
23
+ mode = "default",
24
+ size = "m",
25
+ pageSize,
26
+ defaultPageSize = 20,
27
+ pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
28
+ onPageSizeChange,
29
+ showPageSize = false,
30
+ showQuickJump = false,
31
+ helperText,
32
+ previousLabel = "Previous page",
33
+ nextLabel = "Next page",
34
+ pageLabel = defaultPageLabel,
35
+ quickJumpLabel = "Go to page",
36
+ pageSizeLabel = "Per page",
37
+ className,
38
+ ref,
39
+ "aria-label": ariaLabel = "Pagination"
40
+ } = _a,
41
+ props = __rest(_a, ["totalPages", "page", "defaultPage", "onPageChange", "siblingCount", "boundaryCount", "disabledPages", "disabled", "mode", "size", "pageSize", "defaultPageSize", "pageSizeOptions", "onPageSizeChange", "showPageSize", "showQuickJump", "helperText", "previousLabel", "nextLabel", "pageLabel", "quickJumpLabel", "pageSizeLabel", "className", "ref", "aria-label"]);
42
+ const total = toTotalPages(totalPages);
43
+ const [internalPage, setInternalPage] = useState(() => clamp(defaultPage, 1, total));
44
+ const currentPage = clamp(page !== null && page !== void 0 ? page : internalPage, 1, total);
45
+ const [internalPageSize, setInternalPageSize] = useState(defaultPageSize);
46
+ const currentPageSize = pageSize !== null && pageSize !== void 0 ? pageSize : internalPageSize;
47
+ const [quickJumpDraft, setQuickJumpDraft] = useState(null);
48
+ const disabledSet = useMemo(() => new Set(disabledPages), [disabledPages]);
49
+ const items = useMemo(() => getPaginationItems(total, currentPage, siblingCount, boundaryCount), [total, currentPage, siblingCount, boundaryCount]);
50
+ const pageSizeSelectOptions = useMemo(() => pageSizeOptions.map(option => ({
51
+ value: String(option),
52
+ label: option
53
+ })), [pageSizeOptions]);
54
+ const previousPage = findEnabledPage(currentPage, -1, total, disabledSet);
55
+ const nextPage = findEnabledPage(currentPage, 1, total, disabledSet);
56
+ const changePage = nextValue => {
57
+ const next = clamp(Math.floor(nextValue), 1, total);
58
+ if (disabled || disabledSet.has(next) || next === currentPage) return;
59
+ if (page === undefined) setInternalPage(next);
60
+ onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(next);
61
+ };
62
+ const commitQuickJump = () => {
63
+ if (quickJumpDraft === null) return;
64
+ const parsed = Number(quickJumpDraft);
65
+ if (quickJumpDraft.trim() !== "" && Number.isInteger(parsed)) {
66
+ changePage(parsed);
67
+ }
68
+ setQuickJumpDraft(null);
69
+ };
70
+ const changePageSize = next => {
71
+ if (pageSize === undefined) setInternalPageSize(next);
72
+ onPageSizeChange === null || onPageSizeChange === void 0 ? void 0 : onPageSizeChange(next);
73
+ };
74
+ const hasActions = showQuickJump || showPageSize || Boolean(helperText);
75
+ return /*#__PURE__*/React.createElement("nav", Object.assign({}, props, {
76
+ ref: ref,
77
+ "aria-label": ariaLabel,
78
+ className: clsx(styles.root, styles[`size_${size}`], {
79
+ [styles.root_disabled]: disabled
80
+ }, className)
81
+ }), /*#__PURE__*/React.createElement("div", {
82
+ className: styles.main
83
+ }, /*#__PURE__*/React.createElement("button", {
84
+ type: "button",
85
+ className: styles.arrow,
86
+ "aria-label": previousLabel,
87
+ disabled: disabled || previousPage === undefined,
88
+ onClick: () => {
89
+ if (previousPage !== undefined) changePage(previousPage);
90
+ }
91
+ }, /*#__PURE__*/React.createElement(Chevron, {
92
+ className: styles.arrow_previous,
93
+ width: 16,
94
+ height: 16,
95
+ "aria-hidden": "true"
96
+ })), mode === "compact" ? (/*#__PURE__*/React.createElement("span", {
97
+ className: styles.counter,
98
+ "aria-live": "polite"
99
+ }, /*#__PURE__*/React.createElement("strong", null, currentPage), /*#__PURE__*/React.createElement("span", null, "/"), total)) : (/*#__PURE__*/React.createElement("div", {
100
+ className: styles.pages
101
+ }, items.map(item => typeof item === "number" ? (/*#__PURE__*/React.createElement("button", {
102
+ type: "button",
103
+ key: item,
104
+ className: clsx(styles.page, {
105
+ [styles.page_current]: item === currentPage
106
+ }),
107
+ "aria-label": pageLabel(item),
108
+ "aria-current": item === currentPage ? "page" : undefined,
109
+ disabled: disabled || disabledSet.has(item),
110
+ onClick: () => changePage(item)
111
+ }, item)) : (/*#__PURE__*/React.createElement("span", {
112
+ key: item,
113
+ className: styles.ellipsis,
114
+ "aria-hidden": "true"
115
+ }, "\u2026"))))), /*#__PURE__*/React.createElement("button", {
116
+ type: "button",
117
+ className: styles.arrow,
118
+ "aria-label": nextLabel,
119
+ disabled: disabled || nextPage === undefined,
120
+ onClick: () => {
121
+ if (nextPage !== undefined) changePage(nextPage);
122
+ }
123
+ }, /*#__PURE__*/React.createElement(Chevron, {
124
+ className: styles.arrow_next,
125
+ width: 16,
126
+ height: 16,
127
+ "aria-hidden": "true"
128
+ }))), hasActions && (/*#__PURE__*/React.createElement("div", {
129
+ className: styles.actions
130
+ }, showQuickJump && (/*#__PURE__*/React.createElement(Input, {
131
+ className: styles.jump,
132
+ size: size,
133
+ label: quickJumpLabel,
134
+ inputMode: "numeric",
135
+ value: quickJumpDraft !== null && quickJumpDraft !== void 0 ? quickJumpDraft : String(currentPage),
136
+ disabled: disabled,
137
+ onChange: (_, value) => setQuickJumpDraft(value),
138
+ onBlur: commitQuickJump,
139
+ onKeyDown: event => {
140
+ if (event.key === "Enter") commitQuickJump();
141
+ }
142
+ })), showPageSize && (/*#__PURE__*/React.createElement(Select, {
143
+ size: size,
144
+ label: pageSizeLabel,
145
+ options: pageSizeSelectOptions,
146
+ value: String(currentPageSize),
147
+ disabled: disabled,
148
+ onChange: value => changePageSize(Number(value))
149
+ })), helperText && /*#__PURE__*/React.createElement("div", {
150
+ className: styles.helper
151
+ }, helperText))));
152
+ };
153
+
154
+ export { Pagination };
@@ -0,0 +1,118 @@
1
+ .root {
2
+ --pagination-item-size: var(--component-input-height-m);
3
+ --pagination-font-size: var(--font-size-text-md);
4
+ color: var(--component-pagination-text);
5
+ display: inline-flex;
6
+ flex-direction: column;
7
+ font-family: var(--font-family-ui);
8
+ gap: var(--space-3);
9
+ max-inline-size: 100%;
10
+ }
11
+ .root_disabled {
12
+ opacity: 0.55;
13
+ }
14
+
15
+ .size_s {
16
+ --pagination-item-size: var(--component-input-height-s);
17
+ --pagination-font-size: var(--font-size-text-sm);
18
+ }
19
+ .size_l {
20
+ --pagination-item-size: var(--component-input-height-l);
21
+ }
22
+
23
+ .main,
24
+ .pages {
25
+ align-items: center;
26
+ display: flex;
27
+ gap: var(--space-1);
28
+ }
29
+
30
+ .page,
31
+ .arrow {
32
+ align-items: center;
33
+ background: transparent;
34
+ block-size: var(--pagination-item-size);
35
+ border: none;
36
+ border-radius: var(--radius-sm);
37
+ box-sizing: border-box;
38
+ color: inherit;
39
+ cursor: pointer;
40
+ display: inline-flex;
41
+ font: inherit;
42
+ font-size: var(--pagination-font-size);
43
+ justify-content: center;
44
+ min-inline-size: var(--pagination-item-size);
45
+ padding: 0 var(--space-2);
46
+ }
47
+ .page:disabled,
48
+ .arrow:disabled {
49
+ color: var(--color-text-disabled);
50
+ cursor: not-allowed;
51
+ }
52
+ .page:focus-visible,
53
+ .arrow:focus-visible {
54
+ outline: var(--focus-ring-width) solid var(--component-pagination-focus-ring);
55
+ outline-offset: var(--focus-ring-width);
56
+ }
57
+ .page:hover:not(:disabled),
58
+ .arrow:hover:not(:disabled) {
59
+ background: var(--component-pagination-bg-hover);
60
+ }
61
+
62
+ .page_current,
63
+ .page_current:hover:not(:disabled) {
64
+ background: var(--component-pagination-bg-current);
65
+ color: var(--component-pagination-text-current);
66
+ cursor: default;
67
+ }
68
+
69
+ .arrow_previous {
70
+ transform: rotate(90deg);
71
+ }
72
+
73
+ .arrow_next {
74
+ transform: rotate(-90deg);
75
+ }
76
+
77
+ .ellipsis {
78
+ align-items: center;
79
+ block-size: var(--pagination-item-size);
80
+ color: var(--color-text-secondary);
81
+ display: inline-flex;
82
+ font-size: var(--pagination-font-size);
83
+ justify-content: center;
84
+ min-inline-size: var(--space-6);
85
+ -webkit-user-select: none;
86
+ user-select: none;
87
+ }
88
+
89
+ .counter {
90
+ align-items: center;
91
+ display: flex;
92
+ font-size: var(--pagination-font-size);
93
+ gap: var(--space-1);
94
+ justify-content: center;
95
+ min-inline-size: var(--space-20);
96
+ }
97
+
98
+ .actions {
99
+ align-items: flex-end;
100
+ display: flex;
101
+ flex-wrap: wrap;
102
+ gap: var(--space-4);
103
+ min-inline-size: 0;
104
+ }
105
+
106
+ .jump {
107
+ inline-size: var(--space-20);
108
+ }
109
+
110
+ .helper {
111
+ color: var(--color-text-secondary);
112
+ font-size: var(--font-size-text-sm);
113
+ line-height: var(--pagination-item-size);
114
+ min-inline-size: 0;
115
+ overflow: hidden;
116
+ text-overflow: ellipsis;
117
+ white-space: nowrap;
118
+ }
@@ -0,0 +1,5 @@
1
+ import type { PaginationItem } from "./index.props";
2
+ export declare const clamp: (value: number, min: number, max: number) => number;
3
+ export declare const toTotalPages: (totalPages: number) => number;
4
+ export declare const getPaginationItems: (totalPages: number, page: number, siblingCount?: number, boundaryCount?: number) => PaginationItem[];
5
+ export declare const findEnabledPage: (from: number, direction: -1 | 1, total: number, disabledPages: ReadonlySet<number>) => number | undefined;
@@ -0,0 +1,31 @@
1
+ const range = (start, end) => Array.from({
2
+ length: Math.max(0, end - start + 1)
3
+ }, (_, index) => start + index);
4
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
5
+ const toTotalPages = totalPages => Math.max(1, Math.floor(totalPages) || 1);
6
+ const getPaginationItems = (totalPages, page, siblingCount = 1, boundaryCount = 1) => {
7
+ const total = toTotalPages(totalPages);
8
+ const current = clamp(Math.floor(page) || 1, 1, total);
9
+ const siblings = Math.max(0, Math.floor(siblingCount));
10
+ const boundaries = Math.max(0, Math.floor(boundaryCount));
11
+ const pages = [...new Set([...range(1, Math.min(boundaries, total)), ...range(Math.max(1, current - siblings), Math.min(total, current + siblings)), ...range(Math.max(1, total - boundaries + 1), total)])].sort((left, right) => left - right);
12
+ return pages.flatMap((visiblePage, index) => {
13
+ const previous = pages[index - 1];
14
+ if (previous === undefined) return [visiblePage];
15
+ if (visiblePage - previous === 2) return [previous + 1, visiblePage];
16
+ if (visiblePage - previous > 2) {
17
+ const ellipsis = visiblePage > current ? "end-ellipsis" : "start-ellipsis";
18
+ return [ellipsis, visiblePage];
19
+ }
20
+ return [visiblePage];
21
+ });
22
+ };
23
+ const findEnabledPage = (from, direction, total, disabledPages) => {
24
+ let candidate = from + direction;
25
+ while (candidate >= 1 && candidate <= total && disabledPages.has(candidate)) {
26
+ candidate += direction;
27
+ }
28
+ return candidate >= 1 && candidate <= total ? candidate : undefined;
29
+ };
30
+
31
+ export { clamp, findEnabledPage, getPaginationItems, toTotalPages };
@@ -0,0 +1,3 @@
1
+ export { Pagination } from "./Pagination";
2
+ export { getPaginationItems } from "./helpers";
3
+ export type { PaginationItem, PaginationMode, PaginationProps, PaginationSize, } from "./index.props";
@@ -0,0 +1,2 @@
1
+ export { Pagination } from './Pagination.js';
2
+ export { getPaginationItems } from './helpers.js';
@@ -0,0 +1,31 @@
1
+ import type { HTMLAttributes, ReactNode, Ref } from "react";
2
+ export type PaginationSize = "s" | "m" | "l";
3
+ export type PaginationMode = "default" | "compact";
4
+ export type PaginationItem = number | "start-ellipsis" | "end-ellipsis";
5
+ export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, "onChange" | "defaultValue"> {
6
+ totalPages: number;
7
+ page?: number;
8
+ defaultPage?: number;
9
+ onPageChange?: (page: number) => void;
10
+ /** Pages shown on each side of the current page. */
11
+ siblingCount?: number;
12
+ /** Pages always shown at the start and at the end. */
13
+ boundaryCount?: number;
14
+ disabledPages?: readonly number[];
15
+ disabled?: boolean;
16
+ mode?: PaginationMode;
17
+ size?: PaginationSize;
18
+ pageSize?: number;
19
+ defaultPageSize?: number;
20
+ pageSizeOptions?: readonly number[];
21
+ onPageSizeChange?: (pageSize: number) => void;
22
+ showPageSize?: boolean;
23
+ showQuickJump?: boolean;
24
+ helperText?: ReactNode;
25
+ previousLabel?: string;
26
+ nextLabel?: string;
27
+ pageLabel?: (page: number) => string;
28
+ quickJumpLabel?: ReactNode;
29
+ pageSizeLabel?: ReactNode;
30
+ ref?: Ref<HTMLElement>;
31
+ }
@@ -687,6 +687,11 @@ export declare const THEMES: {
687
687
  "component-calendar-fade-inline-size": string;
688
688
  "component-calendar-day-size": string;
689
689
  "component-calendar-day-radius": string;
690
+ "component-pagination-text": string;
691
+ "component-pagination-text-current": string;
692
+ "component-pagination-bg-current": string;
693
+ "component-pagination-bg-hover": string;
694
+ "component-pagination-focus-ring": string;
690
695
  "component-navigation-item-bg-selected": string;
691
696
  "component-chip-bg-selected": string;
692
697
  "component-chip-bg-selected-strong": string;
@@ -1526,6 +1531,11 @@ export declare const THEMES: {
1526
1531
  "component-calendar-fade-inline-size": string;
1527
1532
  "component-calendar-day-size": string;
1528
1533
  "component-calendar-day-radius": string;
1534
+ "component-pagination-text": string;
1535
+ "component-pagination-text-current": string;
1536
+ "component-pagination-bg-current": string;
1537
+ "component-pagination-bg-hover": string;
1538
+ "component-pagination-focus-ring": string;
1529
1539
  "component-navigation-item-bg-selected": string;
1530
1540
  "component-chip-bg-selected": string;
1531
1541
  "component-chip-bg-selected-strong": string;
@@ -608,6 +608,11 @@ declare const _default: {
608
608
  "component-calendar-fade-inline-size": string;
609
609
  "component-calendar-day-size": string;
610
610
  "component-calendar-day-radius": string;
611
+ "component-pagination-text": string;
612
+ "component-pagination-text-current": string;
613
+ "component-pagination-bg-current": string;
614
+ "component-pagination-bg-hover": string;
615
+ "component-pagination-focus-ring": string;
611
616
  "component-navigation-item-bg-selected": string;
612
617
  "component-chip-bg-selected": string;
613
618
  "component-chip-bg-selected-strong": string;
@@ -610,6 +610,11 @@ var THEME_BA = {
610
610
  "component-calendar-fade-inline-size": "2.5rem",
611
611
  "component-calendar-day-size": "2rem",
612
612
  "component-calendar-day-radius": "0.375rem",
613
+ "component-pagination-text": Color.BRAND_BA_FOREST,
614
+ "component-pagination-text-current": Color.BRAND_BA_LIME,
615
+ "component-pagination-bg-current": Color.BRAND_BA_FOREST,
616
+ "component-pagination-bg-hover": Color.NEUTRAL_BA_100,
617
+ "component-pagination-focus-ring": Color.BRAND_BA_LIME,
613
618
  "component-navigation-item-bg-selected": Color.BRAND_BA_FOREST,
614
619
  "component-chip-bg-selected": Color.BRAND_BA_FOREST,
615
620
  "component-chip-bg-selected-strong": Color.BRAND_BA_LIME,
@@ -608,6 +608,11 @@ declare const _default: {
608
608
  "component-calendar-fade-inline-size": string;
609
609
  "component-calendar-day-size": string;
610
610
  "component-calendar-day-radius": string;
611
+ "component-pagination-text": string;
612
+ "component-pagination-text-current": string;
613
+ "component-pagination-bg-current": string;
614
+ "component-pagination-bg-hover": string;
615
+ "component-pagination-focus-ring": string;
611
616
  "component-navigation-item-bg-selected": string;
612
617
  "component-chip-bg-selected": string;
613
618
  "component-chip-bg-selected-strong": string;
@@ -610,6 +610,11 @@ var theme_jaipur = {
610
610
  "component-calendar-fade-inline-size": "2.5rem",
611
611
  "component-calendar-day-size": "2rem",
612
612
  "component-calendar-day-radius": "0.375rem",
613
+ "component-pagination-text": Color.BRAND_JP_EGGPLANT,
614
+ "component-pagination-text-current": Color.BRAND_JP_PINK,
615
+ "component-pagination-bg-current": Color.BRAND_JP_EGGPLANT,
616
+ "component-pagination-bg-hover": Color.NEUTRAL_JP_100,
617
+ "component-pagination-focus-ring": Color.BRAND_JP_PINK,
613
618
  "component-navigation-item-bg-selected": Color.BRAND_JP_EGGPLANT,
614
619
  "component-chip-bg-selected": Color.BRAND_JP_EGGPLANT,
615
620
  "component-chip-bg-selected-strong": Color.BRAND_JP_PINK,
@@ -1,2 +1,7 @@
1
- import type { RefObject } from "react";
2
- export declare function useTrapFocus(refObject: RefObject<HTMLElement | null>, isActive?: () => boolean): void;
1
+ /**
2
+ * Moves focus into `element` when it mounts (first focusable element, or the
3
+ * element itself, which needs tabIndex={-1}), keeps Tab inside it while
4
+ * `isActive` returns true, and returns focus to the previously focused
5
+ * element when it unmounts.
6
+ */
7
+ export declare function useTrapFocus(element: HTMLElement | null, isActive?: () => boolean): void;
@@ -1,15 +1,70 @@
1
- import { useEffect } from 'react';
1
+ import { useRef, useEffect } from 'react';
2
2
 
3
- const getFocusableModalElements = element => element.querySelectorAll('a[href], button, textarea, input[type="text"], input[type="password"], input[type="radio"], input[type="checkbox"], select');
4
- function useTrapFocus(refObject, isActive) {
3
+ const candidateSelector = ["a[href]", "button", "textarea", 'input:not([type="hidden"])', "select", "[tabindex]", '[role="button"]'].join(",");
4
+ // The selector only finds candidates: `[tabindex]` also matches disabled
5
+ // controls and `[role="button"]` alone does not make an element focusable.
6
+ const isTabbable = node => node.tabIndex >= 0 && !node.matches(":disabled");
7
+ const getFocusableElements = element => Array.from(element.querySelectorAll(candidateSelector)).filter(isTabbable);
8
+ // Open traps in mount order, so a closing trap can hand its saved focus to
9
+ // traps opened on top of it.
10
+ const focusStack = [];
11
+ /**
12
+ * Moves focus into `element` when it mounts (first focusable element, or the
13
+ * element itself, which needs tabIndex={-1}), keeps Tab inside it while
14
+ * `isActive` returns true, and returns focus to the previously focused
15
+ * element when it unmounts.
16
+ */
17
+ function useTrapFocus(element, isActive) {
18
+ // Read during render, before children with autoFocus take focus.
19
+ const previousFocusRef = useRef(null);
20
+ if (!element && typeof document !== "undefined") {
21
+ previousFocusRef.current = document.activeElement;
22
+ }
5
23
  useEffect(() => {
24
+ var _a;
25
+ if (!element) return;
26
+ const entry = {
27
+ element,
28
+ previousFocus: previousFocusRef.current
29
+ };
30
+ focusStack.push(entry);
31
+ if (!element.contains(document.activeElement)) {
32
+ ((_a = getFocusableElements(element)[0]) !== null && _a !== void 0 ? _a : element).focus();
33
+ }
34
+ return () => {
35
+ focusStack.splice(focusStack.indexOf(entry), 1);
36
+ // Traps opened from inside this one would restore focus to a removed
37
+ // element; give them the focus this trap saved instead.
38
+ focusStack.forEach(other => {
39
+ if (element.contains(other.previousFocus)) {
40
+ other.previousFocus = entry.previousFocus;
41
+ }
42
+ });
43
+ // Focus inside another trap stays there. Focus removed with this
44
+ // element falls back to body.
45
+ const active = document.activeElement;
46
+ const ownsFocus = !active || active === document.body || element.contains(active);
47
+ const {
48
+ previousFocus
49
+ } = entry;
50
+ if (ownsFocus && previousFocus instanceof HTMLElement && previousFocus.isConnected) {
51
+ previousFocus.focus();
52
+ }
53
+ };
54
+ }, [element]);
55
+ useEffect(() => {
56
+ if (!element) return;
6
57
  const handler = event => {
7
- if (!refObject.current || event.code !== "Tab") return;
58
+ if (event.key !== "Tab") return;
8
59
  if (isActive && !isActive()) return;
9
- const focusable = Array.from(getFocusableModalElements(refObject.current));
60
+ const focusable = getFocusableElements(element);
10
61
  const firstElement = focusable[0];
11
62
  const lastElement = focusable[focusable.length - 1];
12
- if (!lastElement || !firstElement) return;
63
+ if (!lastElement || !firstElement) {
64
+ event.preventDefault();
65
+ element.focus();
66
+ return;
67
+ }
13
68
  const focusOutside = document.activeElement ? !focusable.includes(document.activeElement) : true;
14
69
  if (event.shiftKey && (document.activeElement === firstElement || focusOutside)) {
15
70
  lastElement.focus();
@@ -23,7 +78,7 @@ function useTrapFocus(refObject, isActive) {
23
78
  return () => {
24
79
  document.removeEventListener("keydown", handler);
25
80
  };
26
- }, [refObject, isActive]);
81
+ }, [element, isActive]);
27
82
  }
28
83
 
29
84
  export { useTrapFocus };
package/esm/index.d.ts CHANGED
@@ -52,6 +52,8 @@ export { MultiSelect } from "./MultiSelect";
52
52
  export type { MultiSelectProps } from "./MultiSelect/index.props";
53
53
  export { NumberInput } from "./NumberInput";
54
54
  export type { NumberInputGroupStyle, NumberInputProps, NumberInputSize, NumberInputValues, } from "./NumberInput";
55
+ export { Pagination, getPaginationItems } from "./Pagination";
56
+ export type { PaginationItem, PaginationMode, PaginationProps, PaginationSize, } from "./Pagination";
55
57
  export { Portal } from "./Portal";
56
58
  export type { PortalProps } from "./Portal/index.props";
57
59
  export { Progress } from "./Progress";
package/esm/index.js CHANGED
@@ -25,6 +25,8 @@ export { MenuItem } from './Menu/components/MenuItem/index.js';
25
25
  export { Modal } from './Modal/index.js';
26
26
  export { MultiSelect } from './MultiSelect/index.js';
27
27
  export { NumberInput } from './NumberInput/NumberInput.js';
28
+ export { Pagination } from './Pagination/Pagination.js';
29
+ export { getPaginationItems } from './Pagination/helpers.js';
28
30
  export { Portal } from './Portal/index.js';
29
31
  export { Progress } from './Progress/Progress.js';
30
32
  export { RadioGroup } from './RadioGroup/index.js';
@@ -608,6 +608,11 @@
608
608
  --component-calendar-fade-inline-size: 2.5rem;
609
609
  --component-calendar-day-size: 2rem;
610
610
  --component-calendar-day-radius: 0.375rem;
611
+ --component-pagination-text: #163300;
612
+ --component-pagination-text-current: #9FE870;
613
+ --component-pagination-bg-current: #163300;
614
+ --component-pagination-bg-hover: #EDF0EA;
615
+ --component-pagination-focus-ring: #9FE870;
611
616
  --component-navigation-item-bg-selected: #163300;
612
617
  --component-chip-bg-selected: #163300;
613
618
  --component-chip-bg-selected-strong: #9FE870;
@@ -608,6 +608,11 @@
608
608
  --component-calendar-fade-inline-size: 2.5rem;
609
609
  --component-calendar-day-size: 2rem;
610
610
  --component-calendar-day-radius: 0.375rem;
611
+ --component-pagination-text: #2C2242;
612
+ --component-pagination-text-current: #FFA8CD;
613
+ --component-pagination-bg-current: #2C2242;
614
+ --component-pagination-bg-hover: #F2F1EE;
615
+ --component-pagination-focus-ring: #FFA8CD;
611
616
  --component-navigation-item-bg-selected: #2C2242;
612
617
  --component-chip-bg-selected: #2C2242;
613
618
  --component-chip-bg-selected-strong: #FFA8CD;
@@ -1,2 +1,7 @@
1
- import type { RefObject } from "react";
2
- export declare function useTrapFocus(refObject: RefObject<HTMLElement | null>, isActive?: () => boolean): void;
1
+ /**
2
+ * Moves focus into `element` when it mounts (first focusable element, or the
3
+ * element itself, which needs tabIndex={-1}), keeps Tab inside it while
4
+ * `isActive` returns true, and returns focus to the previously focused
5
+ * element when it unmounts.
6
+ */
7
+ export declare function useTrapFocus(element: HTMLElement | null, isActive?: () => boolean): void;