@backstage/ui 0.18.0-next.1 → 0.18.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.
- package/CHANGELOG.md +44 -0
- package/dist/components/Avatar/Avatar.esm.js +1 -1
- package/dist/components/Avatar/Avatar.esm.js.map +1 -1
- package/dist/components/Combobox/Combobox.esm.js +2 -0
- package/dist/components/Combobox/Combobox.esm.js.map +1 -1
- package/dist/components/Header/HeaderMetadataStatus.esm.js.map +1 -1
- package/dist/components/PluginHeader/PluginHeader.esm.js +6 -2
- package/dist/components/PluginHeader/PluginHeader.esm.js.map +1 -1
- package/dist/components/Table/hooks/getEffectivePageSize.esm.js +1 -1
- package/dist/components/Table/hooks/getEffectivePageSize.esm.js.map +1 -1
- package/dist/components/Table/hooks/useCompletePagination.esm.js +45 -15
- package/dist/components/Table/hooks/useCompletePagination.esm.js.map +1 -1
- package/dist/components/Table/hooks/useDebouncedReload.esm.js +5 -4
- package/dist/components/Table/hooks/useDebouncedReload.esm.js.map +1 -1
- package/dist/components/Table/hooks/usePageCache.esm.js +7 -4
- package/dist/components/Table/hooks/usePageCache.esm.js.map +1 -1
- package/dist/components/TablePagination/TablePagination.esm.js +5 -2
- package/dist/components/TablePagination/TablePagination.esm.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# @backstage/ui
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d07e99e: Updated BUI links to use the hosting application's client-side router, including relative destinations and the application's configured router base path, while preserving native browser navigation where required.
|
|
8
|
+
|
|
9
|
+
**BREAKING**: Anchor-based components no longer accept the React Aria `render` prop. BUI now owns the underlying anchor so routing behavior remains consistent across application and plugin package versions.
|
|
10
|
+
|
|
11
|
+
ListRow, Tag, and table Row now retain client-side navigation when application and plugin packages load separate React Aria copies. Their existing modifier-key, target, download, and link-metadata behavior is unchanged.
|
|
12
|
+
|
|
13
|
+
**Migration:**
|
|
14
|
+
|
|
15
|
+
Remove `render` props from ButtonLink, ComboboxItem, Link, MenuItem, MenuListBoxItem, SearchAutocompleteItem, SelectItem, and Tab. BUI now selects and renders the appropriate anchor or router link automatically.
|
|
16
|
+
|
|
17
|
+
**Affected components:** ButtonLink, Card, ComboboxItem, Header, Link, ListRow, MenuItem, MenuListBoxItem, Row, SearchAutocompleteItem, SelectItem, Tab, Tag
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 736d84e: Use locale-insensitive Unicode casing for consistent string handling across environments.
|
|
22
|
+
|
|
23
|
+
**Affected components:** Avatar
|
|
24
|
+
|
|
25
|
+
- f914343: Prevent BUI styles from overriding document and native control line heights while preserving BUI component typography.
|
|
26
|
+
- 947c612: Fixed `TablePagination` page-size controls to stay synchronized with controlled values and handle empty option lists without crashing.
|
|
27
|
+
|
|
28
|
+
**Affected components:** `TablePagination`
|
|
29
|
+
|
|
30
|
+
- c435198: Fixed an issue where using `PluginHeader` with `FullPage` caused the page to be unexpectedly scrollable. The `--bui-header-height` CSS variable now correctly accounts for the header's `margin-bottom`, preventing the layout from overflowing the viewport.
|
|
31
|
+
- c0b26db: Open `Combobox` suggestions when the input receives focus by default.
|
|
32
|
+
|
|
33
|
+
**Affected components:** Combobox
|
|
34
|
+
|
|
35
|
+
- 0dd80f7: Fixed pagination state handling in `useTable`, including initial offsets, shrinking complete datasets, controlled loading transitions, valid zero and empty cursors, cached error recovery, immediately resolving reloads, and unnecessary reloads when controlled callback identities change.
|
|
36
|
+
|
|
37
|
+
**Affected components:** `useTable`
|
|
38
|
+
|
|
39
|
+
## 0.18.0-next.2
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 0dd80f7: Fixed pagination state handling in `useTable`, including initial offsets, shrinking complete datasets, controlled loading transitions, valid zero and empty cursors, cached error recovery, immediately resolving reloads, and unnecessary reloads when controlled callback identities change.
|
|
44
|
+
|
|
45
|
+
**Affected components:** `useTable`
|
|
46
|
+
|
|
3
47
|
## 0.18.0-next.1
|
|
4
48
|
|
|
5
49
|
### Minor Changes
|
|
@@ -22,7 +22,7 @@ const Avatar = forwardRef((props, ref) => {
|
|
|
22
22
|
};
|
|
23
23
|
}, [src]);
|
|
24
24
|
const initialsCount = ["x-small", "small"].includes(size) ? 1 : 2;
|
|
25
|
-
const initials = name.split(" ").map((word) => word[0]).join("").
|
|
25
|
+
const initials = name.split(" ").map((word) => word[0]).join("").toUpperCase().slice(0, initialsCount);
|
|
26
26
|
return /* @__PURE__ */ jsx(
|
|
27
27
|
"div",
|
|
28
28
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Avatar.esm.js","sources":["../../../src/components/Avatar/Avatar.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { forwardRef, useState, useEffect } from 'react';\nimport { AvatarProps } from './types';\nimport { useDefinition } from '../../hooks/useDefinition';\nimport { AvatarDefinition } from './definition';\n\n/**\n * Displays a user's profile image with an automatic fallback to their initials when the image fails to load.\n *\n * @public\n */\nexport const Avatar = forwardRef<HTMLDivElement, AvatarProps>((props, ref) => {\n const { ownProps, restProps, dataAttributes } = useDefinition(\n AvatarDefinition,\n props,\n );\n\n const { classes, size, src, name, purpose } = ownProps;\n\n const [imageStatus, setImageStatus] = useState<\n 'loading' | 'loaded' | 'error'\n >('loading');\n\n useEffect(() => {\n setImageStatus('loading');\n const img = new Image();\n img.onload = () => setImageStatus('loaded');\n img.onerror = () => setImageStatus('error');\n img.src = src;\n\n return () => {\n img.onload = null;\n img.onerror = null;\n };\n }, [src]);\n\n const initialsCount = ['x-small', 'small'].includes(size) ? 1 : 2;\n\n const initials = name\n .split(' ')\n .map(word => word[0])\n .join('')\n .
|
|
1
|
+
{"version":3,"file":"Avatar.esm.js","sources":["../../../src/components/Avatar/Avatar.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { forwardRef, useState, useEffect } from 'react';\nimport { AvatarProps } from './types';\nimport { useDefinition } from '../../hooks/useDefinition';\nimport { AvatarDefinition } from './definition';\n\n/**\n * Displays a user's profile image with an automatic fallback to their initials when the image fails to load.\n *\n * @public\n */\nexport const Avatar = forwardRef<HTMLDivElement, AvatarProps>((props, ref) => {\n const { ownProps, restProps, dataAttributes } = useDefinition(\n AvatarDefinition,\n props,\n );\n\n const { classes, size, src, name, purpose } = ownProps;\n\n const [imageStatus, setImageStatus] = useState<\n 'loading' | 'loaded' | 'error'\n >('loading');\n\n useEffect(() => {\n setImageStatus('loading');\n const img = new Image();\n img.onload = () => setImageStatus('loaded');\n img.onerror = () => setImageStatus('error');\n img.src = src;\n\n return () => {\n img.onload = null;\n img.onerror = null;\n };\n }, [src]);\n\n const initialsCount = ['x-small', 'small'].includes(size) ? 1 : 2;\n\n const initials = name\n .split(' ')\n .map(word => word[0])\n .join('')\n .toUpperCase()\n .slice(0, initialsCount);\n\n return (\n <div\n ref={ref}\n role=\"img\"\n aria-label={purpose === 'informative' ? name : undefined}\n aria-hidden={purpose === 'decoration' ? true : undefined}\n className={classes.root}\n {...dataAttributes}\n {...restProps}\n >\n {imageStatus === 'loaded' ? (\n <img src={src} alt=\"\" className={classes.image} />\n ) : (\n <div aria-hidden=\"true\" className={classes.fallback}>\n {initials}\n </div>\n )}\n </div>\n );\n});\n\nAvatar.displayName = 'Avatar';\n"],"names":[],"mappings":";;;;;AA0BO,MAAM,MAAA,GAAS,UAAA,CAAwC,CAAC,KAAA,EAAO,GAAA,KAAQ;AAC5E,EAAA,MAAM,EAAE,QAAA,EAAU,SAAA,EAAW,cAAA,EAAe,GAAI,aAAA;AAAA,IAC9C,gBAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,EAAE,OAAA,EAAS,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,SAAQ,GAAI,QAAA;AAE9C,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAEpC,SAAS,CAAA;AAEX,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,cAAA,CAAe,SAAS,CAAA;AACxB,IAAA,MAAM,GAAA,GAAM,IAAI,KAAA,EAAM;AACtB,IAAA,GAAA,CAAI,MAAA,GAAS,MAAM,cAAA,CAAe,QAAQ,CAAA;AAC1C,IAAA,GAAA,CAAI,OAAA,GAAU,MAAM,cAAA,CAAe,OAAO,CAAA;AAC1C,IAAA,GAAA,CAAI,GAAA,GAAM,GAAA;AAEV,IAAA,OAAO,MAAM;AACX,MAAA,GAAA,CAAI,MAAA,GAAS,IAAA;AACb,MAAA,GAAA,CAAI,OAAA,GAAU,IAAA;AAAA,IAChB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,GAAG,CAAC,CAAA;AAER,EAAA,MAAM,aAAA,GAAgB,CAAC,SAAA,EAAW,OAAO,EAAE,QAAA,CAAS,IAAI,IAAI,CAAA,GAAI,CAAA;AAEhE,EAAA,MAAM,WAAW,IAAA,CACd,KAAA,CAAM,GAAG,CAAA,CACT,GAAA,CAAI,UAAQ,IAAA,CAAK,CAAC,CAAC,CAAA,CACnB,KAAK,EAAE,CAAA,CACP,aAAY,CACZ,KAAA,CAAM,GAAG,aAAa,CAAA;AAEzB,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,KAAA;AAAA,MACL,YAAA,EAAY,OAAA,KAAY,aAAA,GAAgB,IAAA,GAAO,MAAA;AAAA,MAC/C,aAAA,EAAa,OAAA,KAAY,YAAA,GAAe,IAAA,GAAO,MAAA;AAAA,MAC/C,WAAW,OAAA,CAAQ,IAAA;AAAA,MAClB,GAAG,cAAA;AAAA,MACH,GAAG,SAAA;AAAA,MAEH,0BAAgB,QAAA,mBACf,GAAA,CAAC,SAAI,GAAA,EAAU,GAAA,EAAI,IAAG,SAAA,EAAW,OAAA,CAAQ,KAAA,EAAO,CAAA,uBAE/C,KAAA,EAAA,EAAI,aAAA,EAAY,QAAO,SAAA,EAAW,OAAA,CAAQ,UACxC,QAAA,EAAA,QAAA,EACH;AAAA;AAAA,GAEJ;AAEJ,CAAC;AAED,MAAA,CAAO,WAAA,GAAc,QAAA;;;;"}
|
|
@@ -98,6 +98,7 @@ function ComboboxImpl(props, ref) {
|
|
|
98
98
|
inputValue,
|
|
99
99
|
defaultInputValue,
|
|
100
100
|
onInputChange,
|
|
101
|
+
menuTrigger = "focus",
|
|
101
102
|
...ariaProps
|
|
102
103
|
} = restProps;
|
|
103
104
|
const asyncComboboxProps = isDirectAsyncServer ? {
|
|
@@ -133,6 +134,7 @@ function ComboboxImpl(props, ref) {
|
|
|
133
134
|
ref,
|
|
134
135
|
...ariaProps,
|
|
135
136
|
...comboboxStateProps,
|
|
137
|
+
menuTrigger,
|
|
136
138
|
allowsEmptyCollection: true,
|
|
137
139
|
children: [
|
|
138
140
|
/* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Combobox.esm.js","sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { forwardRef, useEffect } from 'react';\nimport { ComboBox as AriaComboBox } from 'react-aria-components';\nimport { useFilter } from 'react-aria';\nimport type {\n ComboboxAsyncItemsProps,\n ComboboxAsyncOptionsProps,\n ComboboxItemsProps,\n ComboboxListBoxOwnProps,\n ComboboxOptionsProps,\n ComboboxProps,\n ComboboxServerItem,\n ComboboxServerItemsProps,\n ComboboxServerOptionsProps,\n ComboboxStaticProps,\n} from './types';\nimport type { Key } from 'react-aria-components';\nimport type {\n AsyncListSource,\n CollectionItem,\n NormalizedOption,\n} from '../../types/selectableCollection';\nimport { useDefinition } from '../../hooks/useDefinition';\nimport { ComboboxDefinition } from './definition';\nimport { Popover } from '../Popover';\nimport { FieldLabel } from '../FieldLabel';\nimport { FieldError } from '../FieldError';\nimport { ComboboxInput } from './ComboboxInput';\nimport { ComboboxListBox } from './ComboboxListBox';\nimport {\n useCollectionAdapter,\n type CollectionAdapterResult,\n} from '../../hooks/useCollectionAdapter';\nimport {\n isAsyncListSource,\n resolveCollectionSource,\n} from '../../utils/selectableCollection';\nimport {\n getAsyncComboboxItemTextValue,\n useAsyncComboboxState,\n} from './useAsyncComboboxState';\n\ntype ComboboxRuntimeStateProps<T extends CollectionItem> = {\n value?: Key | T | null;\n defaultValue?: Key | T | null;\n onChange?: ((value: Key | null) => void) | ((value: T | null) => void);\n inputValue?: string;\n defaultInputValue?: string;\n onInputChange?: (value: string) => void;\n};\n\ntype ComboboxAriaStateProps = {\n value?: Key | null;\n defaultValue?: Key | null;\n onChange?: (value: Key | null) => void;\n inputValue?: string;\n defaultInputValue?: string;\n onInputChange?: (value: string) => void;\n};\n\ntype AsyncComboboxState = {\n value: Key | null;\n inputValue: string;\n onChange: (value: Key | null) => void;\n onInputChange: (value: string) => void;\n};\n\nfunction resolveComboboxStateProps<T extends CollectionItem>({\n runtimeState,\n asyncState,\n collection,\n hasSearch,\n}: {\n runtimeState: ComboboxRuntimeStateProps<T>;\n asyncState?: AsyncComboboxState;\n collection: CollectionAdapterResult<T>;\n hasSearch: boolean;\n}): ComboboxAriaStateProps {\n if (asyncState) {\n return asyncState;\n }\n\n const selectionProps = {\n value: runtimeState.value as Key | null | undefined,\n defaultValue: runtimeState.defaultValue as Key | null | undefined,\n onChange: runtimeState.onChange as\n | ((value: Key | null) => void)\n | undefined,\n };\n\n if (hasSearch) {\n return {\n ...selectionProps,\n inputValue: collection.inputValue,\n defaultInputValue: collection.defaultInputValue,\n onInputChange: collection.onInputChange,\n };\n }\n\n return {\n ...selectionProps,\n inputValue: runtimeState.inputValue,\n defaultInputValue: runtimeState.defaultInputValue,\n onInputChange: runtimeState.onInputChange,\n };\n}\n\n/**\n * A text input combined with a dropdown list of options. The user can type to filter\n * suggestions, navigate with the keyboard, and pick a value. With\n * `allowsCustomValue`, unmatched typed text can remain in the input without\n * selecting an option.\n *\n * @public\n */\nfunction ComboboxImpl<T extends CollectionItem = NormalizedOption>(\n props: ComboboxProps<T>,\n ref: React.ForwardedRef<HTMLDivElement>,\n) {\n const { contains } = useFilter({ sensitivity: 'base' });\n const { ownProps, restProps, dataAttributes } = useDefinition(\n ComboboxDefinition,\n props,\n );\n const {\n classes,\n label,\n description,\n options,\n items,\n children,\n dependencies,\n icon,\n placeholder,\n isRequired,\n secondaryLabel,\n search,\n loading,\n } = ownProps;\n\n const ariaLabel = restProps['aria-label'];\n const ariaLabelledBy = restProps['aria-labelledby'];\n\n useEffect(() => {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n 'Combobox requires either a visible label, aria-label, or aria-labelledby for accessibility',\n );\n }\n }, [label, ariaLabel, ariaLabelledBy]);\n\n const secondaryLabelText = secondaryLabel || (isRequired ? 'Required' : null);\n const collectionSource = resolveCollectionSource<T>({\n options,\n items: items as Iterable<T> | AsyncListSource<T> | undefined,\n });\n const collection = useCollectionAdapter({\n items: collectionSource.source,\n search,\n loading,\n retainSelectedItems: false,\n });\n const renderedItems = collectionSource.rendersItems\n ? collection.canonicalItems\n : undefined;\n const rootItems =\n renderedItems ??\n (collectionSource.options !== undefined && search !== undefined\n ? collection.canonicalItems\n : undefined);\n const searchProps = typeof search === 'object' ? search : undefined;\n const isDirectAsyncServer =\n searchProps?.mode === 'server' &&\n isAsyncListSource(collectionSource.source);\n const shouldDisableRootFilter =\n searchProps?.mode === 'server' || searchProps?.filter !== undefined;\n const {\n value,\n defaultValue,\n onChange,\n inputValue,\n defaultInputValue,\n onInputChange,\n ...ariaProps\n } = restProps as typeof restProps & ComboboxRuntimeStateProps<T>;\n const asyncComboboxProps = isDirectAsyncServer\n ? {\n source: collectionSource.source as AsyncListSource<T>,\n value: value as T | null | undefined,\n defaultValue: defaultValue as T | null | undefined,\n onChange: onChange as ((value: T | null) => void) | undefined,\n allowsCustomValue: restProps.allowsCustomValue,\n }\n : undefined;\n const asyncComboboxState = useAsyncComboboxState(asyncComboboxProps);\n const comboboxStateProps = resolveComboboxStateProps({\n runtimeState: {\n value,\n defaultValue,\n onChange,\n inputValue,\n defaultInputValue,\n onInputChange,\n },\n asyncState: asyncComboboxState,\n collection,\n hasSearch: search !== undefined,\n });\n const defaultFilter = shouldDisableRootFilter ? () => true : contains;\n const getItemTextValue =\n isDirectAsyncServer && items !== undefined\n ? getAsyncComboboxItemTextValue\n : undefined;\n\n return (\n <AriaComboBox<T>\n className={classes.root}\n defaultFilter={defaultFilter}\n items={rootItems}\n {...dataAttributes}\n ref={ref}\n {...ariaProps}\n {...comboboxStateProps}\n allowsEmptyCollection\n >\n <FieldLabel\n label={label}\n secondaryLabel={secondaryLabelText}\n description={description}\n descriptionSlot=\"description\"\n />\n <ComboboxInput icon={icon} placeholder={placeholder} />\n <FieldError />\n <Popover className={classes.popover} hideArrow {...dataAttributes}>\n <ComboboxListBox\n options={collectionSource.options}\n items={renderedItems}\n dependencies={dependencies}\n search={search as never}\n loading={collection.loading}\n isStale={collection.isStale}\n getItemTextValue={getItemTextValue}\n >\n {children as ComboboxListBoxOwnProps<T>['children']}\n </ComboboxListBox>\n </Popover>\n </AriaComboBox>\n );\n}\n\n/** @public */\nexport const Combobox = forwardRef(ComboboxImpl) as unknown as {\n (\n props: ComboboxOptionsProps & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n (\n props: ComboboxAsyncOptionsProps & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n <T extends { id: Key }>(\n props: ComboboxItemsProps<T> & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n <T extends { id: Key }>(\n props: ComboboxAsyncItemsProps<T> & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n (\n props: ComboboxStaticProps & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n (\n props: ComboboxServerOptionsProps & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n <T extends ComboboxServerItem>(\n props: ComboboxServerItemsProps<T> & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n displayName?: string;\n};\n\nCombobox.displayName = 'Combobox';\n"],"names":["AriaComboBox"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Combobox.esm.js","sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { forwardRef, useEffect } from 'react';\nimport { ComboBox as AriaComboBox } from 'react-aria-components';\nimport { useFilter } from 'react-aria';\nimport type {\n ComboboxAsyncItemsProps,\n ComboboxAsyncOptionsProps,\n ComboboxItemsProps,\n ComboboxListBoxOwnProps,\n ComboboxOptionsProps,\n ComboboxProps,\n ComboboxServerItem,\n ComboboxServerItemsProps,\n ComboboxServerOptionsProps,\n ComboboxStaticProps,\n} from './types';\nimport type { Key } from 'react-aria-components';\nimport type {\n AsyncListSource,\n CollectionItem,\n NormalizedOption,\n} from '../../types/selectableCollection';\nimport { useDefinition } from '../../hooks/useDefinition';\nimport { ComboboxDefinition } from './definition';\nimport { Popover } from '../Popover';\nimport { FieldLabel } from '../FieldLabel';\nimport { FieldError } from '../FieldError';\nimport { ComboboxInput } from './ComboboxInput';\nimport { ComboboxListBox } from './ComboboxListBox';\nimport {\n useCollectionAdapter,\n type CollectionAdapterResult,\n} from '../../hooks/useCollectionAdapter';\nimport {\n isAsyncListSource,\n resolveCollectionSource,\n} from '../../utils/selectableCollection';\nimport {\n getAsyncComboboxItemTextValue,\n useAsyncComboboxState,\n} from './useAsyncComboboxState';\n\ntype ComboboxRuntimeStateProps<T extends CollectionItem> = {\n value?: Key | T | null;\n defaultValue?: Key | T | null;\n onChange?: ((value: Key | null) => void) | ((value: T | null) => void);\n inputValue?: string;\n defaultInputValue?: string;\n onInputChange?: (value: string) => void;\n};\n\ntype ComboboxAriaStateProps = {\n value?: Key | null;\n defaultValue?: Key | null;\n onChange?: (value: Key | null) => void;\n inputValue?: string;\n defaultInputValue?: string;\n onInputChange?: (value: string) => void;\n};\n\ntype AsyncComboboxState = {\n value: Key | null;\n inputValue: string;\n onChange: (value: Key | null) => void;\n onInputChange: (value: string) => void;\n};\n\nfunction resolveComboboxStateProps<T extends CollectionItem>({\n runtimeState,\n asyncState,\n collection,\n hasSearch,\n}: {\n runtimeState: ComboboxRuntimeStateProps<T>;\n asyncState?: AsyncComboboxState;\n collection: CollectionAdapterResult<T>;\n hasSearch: boolean;\n}): ComboboxAriaStateProps {\n if (asyncState) {\n return asyncState;\n }\n\n const selectionProps = {\n value: runtimeState.value as Key | null | undefined,\n defaultValue: runtimeState.defaultValue as Key | null | undefined,\n onChange: runtimeState.onChange as\n | ((value: Key | null) => void)\n | undefined,\n };\n\n if (hasSearch) {\n return {\n ...selectionProps,\n inputValue: collection.inputValue,\n defaultInputValue: collection.defaultInputValue,\n onInputChange: collection.onInputChange,\n };\n }\n\n return {\n ...selectionProps,\n inputValue: runtimeState.inputValue,\n defaultInputValue: runtimeState.defaultInputValue,\n onInputChange: runtimeState.onInputChange,\n };\n}\n\n/**\n * A text input combined with a dropdown list of options. The user can type to filter\n * suggestions, navigate with the keyboard, and pick a value. With\n * `allowsCustomValue`, unmatched typed text can remain in the input without\n * selecting an option. The suggestions open when the input receives focus by\n * default. Use `menuTrigger` to customize this behavior.\n *\n * @public\n */\nfunction ComboboxImpl<T extends CollectionItem = NormalizedOption>(\n props: ComboboxProps<T>,\n ref: React.ForwardedRef<HTMLDivElement>,\n) {\n const { contains } = useFilter({ sensitivity: 'base' });\n const { ownProps, restProps, dataAttributes } = useDefinition(\n ComboboxDefinition,\n props,\n );\n const {\n classes,\n label,\n description,\n options,\n items,\n children,\n dependencies,\n icon,\n placeholder,\n isRequired,\n secondaryLabel,\n search,\n loading,\n } = ownProps;\n\n const ariaLabel = restProps['aria-label'];\n const ariaLabelledBy = restProps['aria-labelledby'];\n\n useEffect(() => {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n 'Combobox requires either a visible label, aria-label, or aria-labelledby for accessibility',\n );\n }\n }, [label, ariaLabel, ariaLabelledBy]);\n\n const secondaryLabelText = secondaryLabel || (isRequired ? 'Required' : null);\n const collectionSource = resolveCollectionSource<T>({\n options,\n items: items as Iterable<T> | AsyncListSource<T> | undefined,\n });\n const collection = useCollectionAdapter({\n items: collectionSource.source,\n search,\n loading,\n retainSelectedItems: false,\n });\n const renderedItems = collectionSource.rendersItems\n ? collection.canonicalItems\n : undefined;\n const rootItems =\n renderedItems ??\n (collectionSource.options !== undefined && search !== undefined\n ? collection.canonicalItems\n : undefined);\n const searchProps = typeof search === 'object' ? search : undefined;\n const isDirectAsyncServer =\n searchProps?.mode === 'server' &&\n isAsyncListSource(collectionSource.source);\n const shouldDisableRootFilter =\n searchProps?.mode === 'server' || searchProps?.filter !== undefined;\n const {\n value,\n defaultValue,\n onChange,\n inputValue,\n defaultInputValue,\n onInputChange,\n menuTrigger = 'focus',\n ...ariaProps\n } = restProps as typeof restProps & ComboboxRuntimeStateProps<T>;\n const asyncComboboxProps = isDirectAsyncServer\n ? {\n source: collectionSource.source as AsyncListSource<T>,\n value: value as T | null | undefined,\n defaultValue: defaultValue as T | null | undefined,\n onChange: onChange as ((value: T | null) => void) | undefined,\n allowsCustomValue: restProps.allowsCustomValue,\n }\n : undefined;\n const asyncComboboxState = useAsyncComboboxState(asyncComboboxProps);\n const comboboxStateProps = resolveComboboxStateProps({\n runtimeState: {\n value,\n defaultValue,\n onChange,\n inputValue,\n defaultInputValue,\n onInputChange,\n },\n asyncState: asyncComboboxState,\n collection,\n hasSearch: search !== undefined,\n });\n const defaultFilter = shouldDisableRootFilter ? () => true : contains;\n const getItemTextValue =\n isDirectAsyncServer && items !== undefined\n ? getAsyncComboboxItemTextValue\n : undefined;\n\n return (\n <AriaComboBox<T>\n className={classes.root}\n defaultFilter={defaultFilter}\n items={rootItems}\n {...dataAttributes}\n ref={ref}\n {...ariaProps}\n {...comboboxStateProps}\n menuTrigger={menuTrigger}\n allowsEmptyCollection\n >\n <FieldLabel\n label={label}\n secondaryLabel={secondaryLabelText}\n description={description}\n descriptionSlot=\"description\"\n />\n <ComboboxInput icon={icon} placeholder={placeholder} />\n <FieldError />\n <Popover className={classes.popover} hideArrow {...dataAttributes}>\n <ComboboxListBox\n options={collectionSource.options}\n items={renderedItems}\n dependencies={dependencies}\n search={search as never}\n loading={collection.loading}\n isStale={collection.isStale}\n getItemTextValue={getItemTextValue}\n >\n {children as ComboboxListBoxOwnProps<T>['children']}\n </ComboboxListBox>\n </Popover>\n </AriaComboBox>\n );\n}\n\n/** @public */\nexport const Combobox = forwardRef(ComboboxImpl) as unknown as {\n (\n props: ComboboxOptionsProps & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n (\n props: ComboboxAsyncOptionsProps & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n <T extends { id: Key }>(\n props: ComboboxItemsProps<T> & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n <T extends { id: Key }>(\n props: ComboboxAsyncItemsProps<T> & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n (\n props: ComboboxStaticProps & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n (\n props: ComboboxServerOptionsProps & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n <T extends ComboboxServerItem>(\n props: ComboboxServerItemsProps<T> & React.RefAttributes<HTMLDivElement>,\n ): React.ReactElement;\n displayName?: string;\n};\n\nCombobox.displayName = 'Combobox';\n"],"names":["AriaComboBox"],"mappings":";;;;;;;;;;;;;;;;;;AAkFA,SAAS,yBAAA,CAAoD;AAAA,EAC3D,YAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA,EAK2B;AACzB,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,OAAO,UAAA;AAAA,EACT;AAEA,EAAA,MAAM,cAAA,GAAiB;AAAA,IACrB,OAAO,YAAA,CAAa,KAAA;AAAA,IACpB,cAAc,YAAA,CAAa,YAAA;AAAA,IAC3B,UAAU,YAAA,CAAa;AAAA,GAGzB;AAEA,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,OAAO;AAAA,MACL,GAAG,cAAA;AAAA,MACH,YAAY,UAAA,CAAW,UAAA;AAAA,MACvB,mBAAmB,UAAA,CAAW,iBAAA;AAAA,MAC9B,eAAe,UAAA,CAAW;AAAA,KAC5B;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,GAAG,cAAA;AAAA,IACH,YAAY,YAAA,CAAa,UAAA;AAAA,IACzB,mBAAmB,YAAA,CAAa,iBAAA;AAAA,IAChC,eAAe,YAAA,CAAa;AAAA,GAC9B;AACF;AAWA,SAAS,YAAA,CACP,OACA,GAAA,EACA;AACA,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,UAAU,EAAE,WAAA,EAAa,QAAQ,CAAA;AACtD,EAAA,MAAM,EAAE,QAAA,EAAU,SAAA,EAAW,cAAA,EAAe,GAAI,aAAA;AAAA,IAC9C,kBAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,KAAA;AAAA,IACA,WAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,YAAA;AAAA,IACA,IAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA,cAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF,GAAI,QAAA;AAEJ,EAAA,MAAM,SAAA,GAAY,UAAU,YAAY,CAAA;AACxC,EAAA,MAAM,cAAA,GAAiB,UAAU,iBAAiB,CAAA;AAElD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,KAAA,IAAS,CAAC,SAAA,IAAa,CAAC,cAAA,EAAgB;AAC3C,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN;AAAA,OACF;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,KAAA,EAAO,SAAA,EAAW,cAAc,CAAC,CAAA;AAErC,EAAA,MAAM,kBAAA,GAAqB,cAAA,KAAmB,UAAA,GAAa,UAAA,GAAa,IAAA,CAAA;AACxE,EAAA,MAAM,mBAAmB,uBAAA,CAA2B;AAAA,IAClD,OAAA;AAAA,IACA;AAAA,GACD,CAAA;AACD,EAAA,MAAM,aAAa,oBAAA,CAAqB;AAAA,IACtC,OAAO,gBAAA,CAAiB,MAAA;AAAA,IACxB,MAAA;AAAA,IACA,OAAA;AAAA,IACA,mBAAA,EAAqB;AAAA,GACtB,CAAA;AACD,EAAA,MAAM,aAAA,GAAgB,gBAAA,CAAiB,YAAA,GACnC,UAAA,CAAW,cAAA,GACX,MAAA;AACJ,EAAA,MAAM,SAAA,GACJ,kBACC,gBAAA,CAAiB,OAAA,KAAY,UAAa,MAAA,KAAW,MAAA,GAClD,WAAW,cAAA,GACX,MAAA,CAAA;AACN,EAAA,MAAM,WAAA,GAAc,OAAO,MAAA,KAAW,QAAA,GAAW,MAAA,GAAS,MAAA;AAC1D,EAAA,MAAM,sBACJ,WAAA,EAAa,IAAA,KAAS,QAAA,IACtB,iBAAA,CAAkB,iBAAiB,MAAM,CAAA;AAC3C,EAAA,MAAM,uBAAA,GACJ,WAAA,EAAa,IAAA,KAAS,QAAA,IAAY,aAAa,MAAA,KAAW,MAAA;AAC5D,EAAA,MAAM;AAAA,IACJ,KAAA;AAAA,IACA,YAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA;AAAA,IACA,iBAAA;AAAA,IACA,aAAA;AAAA,IACA,WAAA,GAAc,OAAA;AAAA,IACd,GAAG;AAAA,GACL,GAAI,SAAA;AACJ,EAAA,MAAM,qBAAqB,mBAAA,GACvB;AAAA,IACE,QAAQ,gBAAA,CAAiB,MAAA;AAAA,IACzB,KAAA;AAAA,IACA,YAAA;AAAA,IACA,QAAA;AAAA,IACA,mBAAmB,SAAA,CAAU;AAAA,GAC/B,GACA,MAAA;AACJ,EAAA,MAAM,kBAAA,GAAqB,sBAAsB,kBAAkB,CAAA;AACnE,EAAA,MAAM,qBAAqB,yBAAA,CAA0B;AAAA,IACnD,YAAA,EAAc;AAAA,MACZ,KAAA;AAAA,MACA,YAAA;AAAA,MACA,QAAA;AAAA,MACA,UAAA;AAAA,MACA,iBAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,UAAA,EAAY,kBAAA;AAAA,IACZ,UAAA;AAAA,IACA,WAAW,MAAA,KAAW;AAAA,GACvB,CAAA;AACD,EAAA,MAAM,aAAA,GAAgB,uBAAA,GAA0B,MAAM,IAAA,GAAO,QAAA;AAC7D,EAAA,MAAM,gBAAA,GACJ,mBAAA,IAAuB,KAAA,KAAU,MAAA,GAC7B,6BAAA,GACA,MAAA;AAEN,EAAA,uBACE,IAAA;AAAA,IAACA,QAAA;AAAA,IAAA;AAAA,MACC,WAAW,OAAA,CAAQ,IAAA;AAAA,MACnB,aAAA;AAAA,MACA,KAAA,EAAO,SAAA;AAAA,MACN,GAAG,cAAA;AAAA,MACJ,GAAA;AAAA,MACC,GAAG,SAAA;AAAA,MACH,GAAG,kBAAA;AAAA,MACJ,WAAA;AAAA,MACA,qBAAA,EAAqB,IAAA;AAAA,MAErB,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,KAAA;AAAA,YACA,cAAA,EAAgB,kBAAA;AAAA,YAChB,WAAA;AAAA,YACA,eAAA,EAAgB;AAAA;AAAA,SAClB;AAAA,wBACA,GAAA,CAAC,aAAA,EAAA,EAAc,IAAA,EAAY,WAAA,EAA0B,CAAA;AAAA,4BACpD,UAAA,EAAA,EAAW,CAAA;AAAA,wBACZ,GAAA,CAAC,WAAQ,SAAA,EAAW,OAAA,CAAQ,SAAS,SAAA,EAAS,IAAA,EAAE,GAAG,cAAA,EACjD,QAAA,kBAAA,GAAA;AAAA,UAAC,eAAA;AAAA,UAAA;AAAA,YACC,SAAS,gBAAA,CAAiB,OAAA;AAAA,YAC1B,KAAA,EAAO,aAAA;AAAA,YACP,YAAA;AAAA,YACA,MAAA;AAAA,YACA,SAAS,UAAA,CAAW,OAAA;AAAA,YACpB,SAAS,UAAA,CAAW,OAAA;AAAA,YACpB,gBAAA;AAAA,YAEC;AAAA;AAAA,SACH,EACF;AAAA;AAAA;AAAA,GACF;AAEJ;AAGO,MAAM,QAAA,GAAW,WAAW,YAAY;AAyB/C,QAAA,CAAS,WAAA,GAAc,UAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeaderMetadataStatus.esm.js","sources":["../../../src/components/Header/HeaderMetadataStatus.tsx"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HeaderMetadataStatusProps } from './types';\nimport { Text } from '../Text';\nimport { Link } from '../Link';\nimport styles from './HeaderMetadataStatus.module.css';\n\n/**\n * Displays a single status indicator as a
|
|
1
|
+
{"version":3,"file":"HeaderMetadataStatus.esm.js","sources":["../../../src/components/Header/HeaderMetadataStatus.tsx"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HeaderMetadataStatusProps } from './types';\nimport { Text } from '../Text';\nimport { Link } from '../Link';\nimport styles from './HeaderMetadataStatus.module.css';\n\n/**\n * Displays a single status indicator as a colored dot with a label inside a\n * Header metadata value. Optionally renders the label as a link when href is provided.\n *\n * @public\n */\nexport const HeaderMetadataStatus = ({\n label,\n color,\n href,\n}: HeaderMetadataStatusProps) => {\n return (\n <div className={styles.single}>\n <span\n aria-hidden=\"true\"\n className={`${styles.dot} ${styles[`dot-${color}`]}`}\n />\n <Text variant=\"body-medium\">\n {href ? (\n <Link href={href} standalone>\n {label}\n </Link>\n ) : (\n label\n )}\n </Text>\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AA2BO,MAAM,uBAAuB,CAAC;AAAA,EACnC,KAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,KAAiC;AAC/B,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,MAAA,CAAO,MAAA,EACrB,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,aAAA,EAAY,MAAA;AAAA,QACZ,SAAA,EAAW,GAAG,MAAA,CAAO,GAAG,IAAI,MAAA,CAAO,CAAA,IAAA,EAAO,KAAK,CAAA,CAAE,CAAC,CAAA;AAAA;AAAA,KACpD;AAAA,oBACA,GAAA,CAAC,IAAA,EAAA,EAAK,OAAA,EAAQ,aAAA,EACX,QAAA,EAAA,IAAA,mBACC,GAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAY,UAAA,EAAU,IAAA,EACzB,QAAA,EAAA,KAAA,EACH,CAAA,GAEA,KAAA,EAEJ;AAAA,GAAA,EACF,CAAA;AAEJ;;;;"}
|
|
@@ -58,14 +58,18 @@ const PluginHeader = (props) => {
|
|
|
58
58
|
`${height}px`
|
|
59
59
|
);
|
|
60
60
|
};
|
|
61
|
+
const getHeight = () => {
|
|
62
|
+
const marginBottom = parseFloat(window.getComputedStyle(el).marginBottom) || 0;
|
|
63
|
+
return el.offsetHeight + marginBottom;
|
|
64
|
+
};
|
|
61
65
|
const scheduleHeightUpdate = () => {
|
|
62
66
|
cancelScheduledUpdate();
|
|
63
67
|
animationFrameRef.current = requestAnimationFrame(() => {
|
|
64
68
|
animationFrameRef.current = void 0;
|
|
65
|
-
applyHeight(
|
|
69
|
+
applyHeight(getHeight());
|
|
66
70
|
});
|
|
67
71
|
};
|
|
68
|
-
applyHeight(
|
|
72
|
+
applyHeight(getHeight());
|
|
69
73
|
if (typeof ResizeObserver === "undefined") {
|
|
70
74
|
return () => {
|
|
71
75
|
cancelScheduledUpdate();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginHeader.esm.js","sources":["../../../src/components/PluginHeader/PluginHeader.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginHeaderProps } from './types';\nimport { Tabs, TabList, Tab } from '../Tabs';\nimport { useDefinition } from '../../hooks/useDefinition';\nimport { PluginHeaderDefinition } from './definition';\nimport { type NavigateOptions } from 'react-router-dom';\nimport { Children, useMemo, useRef } from 'react';\nimport { useIsomorphicLayoutEffect } from '../../hooks/useIsomorphicLayoutEffect';\nimport { Box } from '../Box';\nimport { Link } from '../Link';\nimport { RiShapesLine } from '@remixicon/react';\nimport { Text } from '../Text';\nimport { VisuallyHidden } from '../VisuallyHidden';\nimport { PluginHeaderBreadcrumbs } from './PluginHeaderBreadcrumbs';\n\ndeclare module 'react-aria-components' {\n interface RouterConfig {\n routerOptions: NavigateOptions;\n }\n}\n\n/**\n * Renders a plugin header with icon, title, custom actions, and optional tabs.\n * Always participates in the background context system so descendants (e.g. buttons)\n * get the correct `data-on-bg` styling inside the toolbar and tabs.\n *\n * @public\n */\nexport const PluginHeader = (props: PluginHeaderProps) => {\n const { ownProps } = useDefinition(PluginHeaderDefinition, props);\n const {\n classes,\n tabs,\n icon,\n title,\n titleLink,\n breadcrumbs,\n customActions,\n onTabSelectionChange,\n } = ownProps;\n\n const hasTabs = tabs && tabs.length > 0;\n const hasBreadcrumbs = breadcrumbs && breadcrumbs.length > 0;\n const rootRef = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | undefined>(undefined);\n const lastAppliedHeightRef = useRef<number | undefined>(undefined);\n\n const actionChildren = useMemo(() => {\n return Children.toArray(customActions);\n }, [customActions]);\n\n useIsomorphicLayoutEffect(() => {\n const el = rootRef.current;\n if (!el) {\n return undefined;\n }\n\n const cancelScheduledUpdate = () => {\n if (animationFrameRef.current === undefined) {\n return;\n }\n\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = undefined;\n };\n\n const applyHeight = (height: number) => {\n if (lastAppliedHeightRef.current === height) {\n return;\n }\n\n lastAppliedHeightRef.current = height;\n document.documentElement.style.setProperty(\n '--bui-header-height',\n `${height}px`,\n );\n };\n\n const scheduleHeightUpdate = () => {\n cancelScheduledUpdate();\n animationFrameRef.current = requestAnimationFrame(() => {\n animationFrameRef.current = undefined;\n applyHeight(el.offsetHeight);\n });\n };\n\n // Set height once immediately so the initial layout is correct.\n applyHeight(el.offsetHeight);\n\n // Observe for resize changes if ResizeObserver is available\n // (not present in Jest/jsdom by default)\n if (typeof ResizeObserver === 'undefined') {\n return () => {\n cancelScheduledUpdate();\n lastAppliedHeightRef.current = undefined;\n document.documentElement.style.removeProperty('--bui-header-height');\n };\n }\n\n const observer = new ResizeObserver(() => {\n scheduleHeightUpdate();\n });\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n cancelScheduledUpdate();\n lastAppliedHeightRef.current = undefined;\n document.documentElement.style.removeProperty('--bui-header-height');\n };\n }, []);\n\n const titleText = title || 'Your plugin';\n\n return (\n <div ref={rootRef} className={classes.root}>\n <div className={classes.toolbar} data-has-tabs={hasTabs ? '' : undefined}>\n <div className={classes.toolbarContent}>\n <Box bg=\"neutral\" className={classes.toolbarIcon} aria-hidden=\"true\">\n {icon || <RiShapesLine />}\n </Box>\n {hasBreadcrumbs ? (\n <div className={classes.toolbarName}>\n <VisuallyHidden>\n <h1>{titleText}</h1>\n </VisuallyHidden>\n <PluginHeaderBreadcrumbs\n entries={breadcrumbs}\n className={classes.breadcrumbs}\n ellipsisClassName={classes.breadcrumbsEllipsis}\n />\n </div>\n ) : (\n <h1 className={classes.toolbarName}>\n {titleLink ? (\n <Link href={titleLink} standalone variant=\"body-medium\">\n {titleText}\n </Link>\n ) : (\n <Text as=\"span\" variant=\"body-medium\">\n {titleText}\n </Text>\n )}\n </h1>\n )}\n </div>\n <div className={classes.toolbarControls}>{actionChildren}</div>\n </div>\n {hasTabs && (\n <div className={classes.tabs}>\n <Tabs onSelectionChange={onTabSelectionChange}>\n <TabList>\n {tabs?.map(tab => (\n <Tab\n key={tab.id}\n id={tab.id}\n href={tab.href}\n matchStrategy={tab.matchStrategy}\n >\n {tab.label}\n </Tab>\n ))}\n </TabList>\n </Tabs>\n </div>\n )}\n </div>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginHeader.esm.js","sources":["../../../src/components/PluginHeader/PluginHeader.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginHeaderProps } from './types';\nimport { Tabs, TabList, Tab } from '../Tabs';\nimport { useDefinition } from '../../hooks/useDefinition';\nimport { PluginHeaderDefinition } from './definition';\nimport { type NavigateOptions } from 'react-router-dom';\nimport { Children, useMemo, useRef } from 'react';\nimport { useIsomorphicLayoutEffect } from '../../hooks/useIsomorphicLayoutEffect';\nimport { Box } from '../Box';\nimport { Link } from '../Link';\nimport { RiShapesLine } from '@remixicon/react';\nimport { Text } from '../Text';\nimport { VisuallyHidden } from '../VisuallyHidden';\nimport { PluginHeaderBreadcrumbs } from './PluginHeaderBreadcrumbs';\n\ndeclare module 'react-aria-components' {\n interface RouterConfig {\n routerOptions: NavigateOptions;\n }\n}\n\n/**\n * Renders a plugin header with icon, title, custom actions, and optional tabs.\n * Always participates in the background context system so descendants (e.g. buttons)\n * get the correct `data-on-bg` styling inside the toolbar and tabs.\n *\n * @public\n */\nexport const PluginHeader = (props: PluginHeaderProps) => {\n const { ownProps } = useDefinition(PluginHeaderDefinition, props);\n const {\n classes,\n tabs,\n icon,\n title,\n titleLink,\n breadcrumbs,\n customActions,\n onTabSelectionChange,\n } = ownProps;\n\n const hasTabs = tabs && tabs.length > 0;\n const hasBreadcrumbs = breadcrumbs && breadcrumbs.length > 0;\n const rootRef = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | undefined>(undefined);\n const lastAppliedHeightRef = useRef<number | undefined>(undefined);\n\n const actionChildren = useMemo(() => {\n return Children.toArray(customActions);\n }, [customActions]);\n\n useIsomorphicLayoutEffect(() => {\n const el = rootRef.current;\n if (!el) {\n return undefined;\n }\n\n const cancelScheduledUpdate = () => {\n if (animationFrameRef.current === undefined) {\n return;\n }\n\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = undefined;\n };\n\n const applyHeight = (height: number) => {\n if (lastAppliedHeightRef.current === height) {\n return;\n }\n\n lastAppliedHeightRef.current = height;\n document.documentElement.style.setProperty(\n '--bui-header-height',\n `${height}px`,\n );\n };\n\n const getHeight = () => {\n const marginBottom =\n parseFloat(window.getComputedStyle(el).marginBottom) || 0;\n return el.offsetHeight + marginBottom;\n };\n\n const scheduleHeightUpdate = () => {\n cancelScheduledUpdate();\n animationFrameRef.current = requestAnimationFrame(() => {\n animationFrameRef.current = undefined;\n applyHeight(getHeight());\n });\n };\n\n // Set height once immediately so the initial layout is correct.\n applyHeight(getHeight());\n\n // Observe for resize changes if ResizeObserver is available\n // (not present in Jest/jsdom by default)\n if (typeof ResizeObserver === 'undefined') {\n return () => {\n cancelScheduledUpdate();\n lastAppliedHeightRef.current = undefined;\n document.documentElement.style.removeProperty('--bui-header-height');\n };\n }\n\n const observer = new ResizeObserver(() => {\n scheduleHeightUpdate();\n });\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n cancelScheduledUpdate();\n lastAppliedHeightRef.current = undefined;\n document.documentElement.style.removeProperty('--bui-header-height');\n };\n }, []);\n\n const titleText = title || 'Your plugin';\n\n return (\n <div ref={rootRef} className={classes.root}>\n <div className={classes.toolbar} data-has-tabs={hasTabs ? '' : undefined}>\n <div className={classes.toolbarContent}>\n <Box bg=\"neutral\" className={classes.toolbarIcon} aria-hidden=\"true\">\n {icon || <RiShapesLine />}\n </Box>\n {hasBreadcrumbs ? (\n <div className={classes.toolbarName}>\n <VisuallyHidden>\n <h1>{titleText}</h1>\n </VisuallyHidden>\n <PluginHeaderBreadcrumbs\n entries={breadcrumbs}\n className={classes.breadcrumbs}\n ellipsisClassName={classes.breadcrumbsEllipsis}\n />\n </div>\n ) : (\n <h1 className={classes.toolbarName}>\n {titleLink ? (\n <Link href={titleLink} standalone variant=\"body-medium\">\n {titleText}\n </Link>\n ) : (\n <Text as=\"span\" variant=\"body-medium\">\n {titleText}\n </Text>\n )}\n </h1>\n )}\n </div>\n <div className={classes.toolbarControls}>{actionChildren}</div>\n </div>\n {hasTabs && (\n <div className={classes.tabs}>\n <Tabs onSelectionChange={onTabSelectionChange}>\n <TabList>\n {tabs?.map(tab => (\n <Tab\n key={tab.id}\n id={tab.id}\n href={tab.href}\n matchStrategy={tab.matchStrategy}\n >\n {tab.label}\n </Tab>\n ))}\n </TabList>\n </Tabs>\n </div>\n )}\n </div>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ function getOptionValue(option) {
|
|
|
4
4
|
}
|
|
5
5
|
function getEffectivePageSize(paginationOptions) {
|
|
6
6
|
const { pageSize, pageSizeOptions } = paginationOptions;
|
|
7
|
-
if (!pageSizeOptions) {
|
|
7
|
+
if (!pageSizeOptions?.length) {
|
|
8
8
|
return pageSize ?? DEFAULT_PAGE_SIZE;
|
|
9
9
|
}
|
|
10
10
|
const firstValue = getOptionValue(pageSizeOptions[0]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getEffectivePageSize.esm.js","sources":["../../../../src/components/Table/hooks/getEffectivePageSize.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PageSizeOption } from '../../TablePagination/types';\nimport type { PaginationOptions } from './types';\n\nconst DEFAULT_PAGE_SIZE = 20;\n\nfunction getOptionValue(option: number | PageSizeOption): number {\n return typeof option === 'number' ? option : option.value;\n}\n\n/** @internal */\nexport function getEffectivePageSize(\n paginationOptions: PaginationOptions,\n): number {\n const { pageSize, pageSizeOptions } = paginationOptions;\n\n if (!pageSizeOptions) {\n return pageSize ?? DEFAULT_PAGE_SIZE;\n }\n\n const firstValue = getOptionValue(pageSizeOptions[0]);\n\n if (pageSize === undefined) {\n return firstValue;\n }\n\n const isValid = pageSizeOptions.some(opt => getOptionValue(opt) === pageSize);\n\n if (isValid) {\n return pageSize;\n }\n\n console.warn(\n `useTable: pageSize ${pageSize} is not in pageSizeOptions, using ${firstValue} instead`,\n );\n return firstValue;\n}\n"],"names":[],"mappings":"AAmBA,MAAM,iBAAA,GAAoB,EAAA;AAE1B,SAAS,eAAe,MAAA,EAAyC;AAC/D,EAAA,OAAO,OAAO,MAAA,KAAW,QAAA,GAAW,MAAA,GAAS,MAAA,CAAO,KAAA;AACtD;AAGO,SAAS,qBACd,iBAAA,EACQ;AACR,EAAA,MAAM,EAAE,QAAA,EAAU,eAAA,EAAgB,GAAI,iBAAA;AAEtC,EAAA,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"getEffectivePageSize.esm.js","sources":["../../../../src/components/Table/hooks/getEffectivePageSize.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PageSizeOption } from '../../TablePagination/types';\nimport type { PaginationOptions } from './types';\n\nconst DEFAULT_PAGE_SIZE = 20;\n\nfunction getOptionValue(option: number | PageSizeOption): number {\n return typeof option === 'number' ? option : option.value;\n}\n\n/** @internal */\nexport function getEffectivePageSize(\n paginationOptions: PaginationOptions,\n): number {\n const { pageSize, pageSizeOptions } = paginationOptions;\n\n if (!pageSizeOptions?.length) {\n return pageSize ?? DEFAULT_PAGE_SIZE;\n }\n\n const firstValue = getOptionValue(pageSizeOptions[0]);\n\n if (pageSize === undefined) {\n return firstValue;\n }\n\n const isValid = pageSizeOptions.some(opt => getOptionValue(opt) === pageSize);\n\n if (isValid) {\n return pageSize;\n }\n\n console.warn(\n `useTable: pageSize ${pageSize} is not in pageSizeOptions, using ${firstValue} instead`,\n );\n return firstValue;\n}\n"],"names":[],"mappings":"AAmBA,MAAM,iBAAA,GAAoB,EAAA;AAE1B,SAAS,eAAe,MAAA,EAAyC;AAC/D,EAAA,OAAO,OAAO,MAAA,KAAW,QAAA,GAAW,MAAA,GAAS,MAAA,CAAO,KAAA;AACtD;AAGO,SAAS,qBACd,iBAAA,EACQ;AACR,EAAA,MAAM,EAAE,QAAA,EAAU,eAAA,EAAgB,GAAI,iBAAA;AAEtC,EAAA,IAAI,CAAC,iBAAiB,MAAA,EAAQ;AAC5B,IAAA,OAAO,QAAA,IAAY,iBAAA;AAAA,EACrB;AAEA,EAAA,MAAM,UAAA,GAAa,cAAA,CAAe,eAAA,CAAgB,CAAC,CAAC,CAAA;AAEpD,EAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,IAAA,OAAO,UAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAU,eAAA,CAAgB,IAAA,CAAK,SAAO,cAAA,CAAe,GAAG,MAAM,QAAQ,CAAA;AAE5E,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,OAAO,QAAA;AAAA,EACT;AAEA,EAAA,OAAA,CAAQ,IAAA;AAAA,IACN,CAAA,mBAAA,EAAsB,QAAQ,CAAA,kCAAA,EAAqC,UAAU,CAAA,QAAA;AAAA,GAC/E;AACA,EAAA,OAAO,UAAA;AACT;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo,
|
|
1
|
+
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
2
2
|
import { useStableCallback } from './useStableCallback.esm.js';
|
|
3
3
|
import { useDebouncedValue } from './useDebouncedValue.esm.js';
|
|
4
4
|
import { getEffectivePageSize } from './getEffectivePageSize.esm.js';
|
|
@@ -26,9 +26,13 @@ function useCompletePagination(options, query) {
|
|
|
26
26
|
const [loadCount, setLoadCount] = useState(0);
|
|
27
27
|
const [offset, setOffset] = useState(initialOffset);
|
|
28
28
|
const [pageSize, setPageSize] = useState(defaultPageSize);
|
|
29
|
+
const previousDefaultPageSize = useRef(defaultPageSize);
|
|
29
30
|
useEffect(() => {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
if (previousDefaultPageSize.current !== defaultPageSize) {
|
|
32
|
+
previousDefaultPageSize.current = defaultPageSize;
|
|
33
|
+
setPageSize(defaultPageSize);
|
|
34
|
+
setOffset(0);
|
|
35
|
+
}
|
|
32
36
|
}, [defaultPageSize]);
|
|
33
37
|
useEffect(() => {
|
|
34
38
|
if (data) {
|
|
@@ -36,6 +40,7 @@ function useCompletePagination(options, query) {
|
|
|
36
40
|
return;
|
|
37
41
|
}
|
|
38
42
|
if (!hasGetData) {
|
|
43
|
+
setIsPending(true);
|
|
39
44
|
return;
|
|
40
45
|
}
|
|
41
46
|
let cancelled = false;
|
|
@@ -98,23 +103,48 @@ function useCompletePagination(options, query) {
|
|
|
98
103
|
searchFn,
|
|
99
104
|
sortFn
|
|
100
105
|
]);
|
|
101
|
-
const
|
|
106
|
+
const previousProcessedDataRef = useRef(processedData);
|
|
107
|
+
if (processedData !== void 0) {
|
|
108
|
+
previousProcessedDataRef.current = processedData;
|
|
109
|
+
}
|
|
110
|
+
const retainedProcessedData = processedData ?? previousProcessedDataRef.current;
|
|
111
|
+
const totalCount = retainedProcessedData?.length ?? 0;
|
|
112
|
+
const effectiveOffset = useMemo(() => {
|
|
113
|
+
if (noPagination) {
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
if (retainedProcessedData === void 0) {
|
|
117
|
+
return offset;
|
|
118
|
+
}
|
|
119
|
+
if (totalCount === 0) {
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
return Math.min(offset, Math.floor((totalCount - 1) / pageSize) * pageSize);
|
|
123
|
+
}, [noPagination, offset, pageSize, retainedProcessedData, totalCount]);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (offset !== effectiveOffset) {
|
|
126
|
+
setOffset(effectiveOffset);
|
|
127
|
+
}
|
|
128
|
+
}, [effectiveOffset, offset]);
|
|
102
129
|
const paginatedData = useMemo(
|
|
103
|
-
() => noPagination ?
|
|
104
|
-
|
|
130
|
+
() => noPagination ? retainedProcessedData : retainedProcessedData?.slice(
|
|
131
|
+
effectiveOffset,
|
|
132
|
+
effectiveOffset + pageSize
|
|
133
|
+
),
|
|
134
|
+
[retainedProcessedData, effectiveOffset, pageSize, noPagination]
|
|
105
135
|
);
|
|
106
|
-
const hasNextPage = !noPagination &&
|
|
107
|
-
const hasPreviousPage = !noPagination &&
|
|
136
|
+
const hasNextPage = !noPagination && effectiveOffset + pageSize < totalCount;
|
|
137
|
+
const hasPreviousPage = !noPagination && effectiveOffset > 0;
|
|
108
138
|
const onNextPage = useCallback(() => {
|
|
109
|
-
if (
|
|
110
|
-
setOffset(
|
|
139
|
+
if (effectiveOffset + pageSize < totalCount) {
|
|
140
|
+
setOffset(effectiveOffset + pageSize);
|
|
111
141
|
}
|
|
112
|
-
}, [
|
|
142
|
+
}, [effectiveOffset, pageSize, totalCount]);
|
|
113
143
|
const onPreviousPage = useCallback(() => {
|
|
114
|
-
if (
|
|
115
|
-
setOffset(Math.max(0,
|
|
144
|
+
if (effectiveOffset > 0) {
|
|
145
|
+
setOffset(Math.max(0, effectiveOffset - pageSize));
|
|
116
146
|
}
|
|
117
|
-
}, [
|
|
147
|
+
}, [effectiveOffset, pageSize]);
|
|
118
148
|
const onPageSizeChange = useCallback((newSize) => {
|
|
119
149
|
setPageSize(newSize);
|
|
120
150
|
setOffset(0);
|
|
@@ -128,7 +158,7 @@ function useCompletePagination(options, query) {
|
|
|
128
158
|
isPending,
|
|
129
159
|
error,
|
|
130
160
|
totalCount,
|
|
131
|
-
offset,
|
|
161
|
+
offset: effectiveOffset,
|
|
132
162
|
pageSize,
|
|
133
163
|
hasNextPage,
|
|
134
164
|
hasPreviousPage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCompletePagination.esm.js","sources":["../../../../src/components/Table/hooks/useCompletePagination.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport type { TableItem } from '../types';\nimport type {\n PaginationResult,\n QueryState,\n UseTableCompleteOptions,\n} from './types';\nimport { useStableCallback } from './useStableCallback';\nimport { useDebouncedValue } from './useDebouncedValue';\nimport { getEffectivePageSize } from './getEffectivePageSize';\n\n/** @internal */\nexport function useCompletePagination<T extends TableItem, TFilter>(\n options: UseTableCompleteOptions<T, TFilter>,\n query: QueryState<TFilter>,\n): PaginationResult<T> & { reload: () => void } {\n const {\n data,\n getData: getDataProp = () => [],\n paginationOptions = {},\n sortFn,\n filterFn,\n searchFn,\n searchDebounceMs = 0,\n filterDebounceMs = 0,\n } = options;\n const hasGetData = 'getData' in options;\n const noPagination = paginationOptions.type === 'none';\n const { initialOffset = 0 } = paginationOptions;\n const defaultPageSize = noPagination\n ? Infinity\n : getEffectivePageSize(paginationOptions);\n\n const getData = useStableCallback(getDataProp);\n const { sort, filter, search } = query;\n\n const [items, setItems] = useState<T[] | undefined>(undefined);\n const [isPending, setIsPending] = useState(!data);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [loadCount, setLoadCount] = useState(0);\n\n const [offset, setOffset] = useState(initialOffset);\n const [pageSize, setPageSize] = useState(defaultPageSize);\n\n // Sync pageSize when the caller changes paginationOptions.pageSize\n useEffect(() => {\n setPageSize(defaultPageSize);\n setOffset(0);\n }, [defaultPageSize]);\n\n // Load data on mount and when loadCount changes (reload trigger)\n useEffect(() => {\n if (data) {\n setIsPending(false);\n return;\n }\n\n if (!hasGetData) {\n return;\n }\n\n let cancelled = false;\n setIsPending(true);\n setError(undefined);\n\n (async () => {\n try {\n const result = getData();\n const resolvedData = result instanceof Promise ? await result : result;\n if (!cancelled) {\n setItems(resolvedData);\n setIsPending(false);\n }\n } catch (err) {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsPending(false);\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [data, getData, hasGetData, loadCount]);\n\n // Debounced surrogates of search and filter feed the processing pipeline.\n // At delayMs === 0 (the default) these are referentially equal to the live\n // values, so behavior is identical to before this refactor.\n const debouncedSearch = useDebouncedValue(search, searchDebounceMs);\n const debouncedFilter = useDebouncedValue(filter, filterDebounceMs);\n\n // Reset offset when the *debounced* query changes — keying on the live query\n // would briefly flash page 1 of unfiltered data while the debounce settles.\n const debouncedQuery = useMemo(\n () => ({ sort, filter: debouncedFilter, search: debouncedSearch }),\n [sort, debouncedFilter, debouncedSearch],\n );\n const prevDebouncedQueryRef = useRef(debouncedQuery);\n useEffect(() => {\n if (prevDebouncedQueryRef.current !== debouncedQuery) {\n prevDebouncedQueryRef.current = debouncedQuery;\n setOffset(0);\n }\n }, [debouncedQuery]);\n\n const resolvedItems = useMemo(() => data ?? items, [data, items]);\n\n // Process data client-side (filter, search, sort)\n const processedData = useMemo(() => {\n if (!resolvedItems) {\n return undefined;\n }\n let result = [...resolvedItems];\n if (debouncedFilter !== undefined && filterFn) {\n result = filterFn(result, debouncedFilter);\n }\n if (debouncedSearch && searchFn) {\n result = searchFn(result, debouncedSearch);\n }\n if (sort && sortFn) {\n result = sortFn(result, sort);\n }\n return result;\n }, [\n resolvedItems,\n sort,\n debouncedFilter,\n debouncedSearch,\n filterFn,\n searchFn,\n sortFn,\n ]);\n\n const totalCount = processedData?.length ?? 0;\n\n // Paginate the processed data\n const paginatedData = useMemo(\n () =>\n noPagination\n ? processedData\n : processedData?.slice(offset, offset + pageSize),\n [processedData, offset, pageSize, noPagination],\n );\n\n const hasNextPage = !noPagination && offset + pageSize < totalCount;\n const hasPreviousPage = !noPagination && offset > 0;\n\n const onNextPage = useCallback(() => {\n if (offset + pageSize < totalCount) {\n setOffset(offset + pageSize);\n }\n }, [offset, pageSize, totalCount]);\n\n const onPreviousPage = useCallback(() => {\n if (offset > 0) {\n setOffset(Math.max(0, offset - pageSize));\n }\n }, [offset, pageSize]);\n\n const onPageSizeChange = useCallback((newSize: number) => {\n setPageSize(newSize);\n setOffset(0);\n }, []);\n\n const reload = useCallback(() => {\n setOffset(0);\n setLoadCount(c => c + 1);\n }, []);\n\n return {\n data: paginatedData,\n isPending: isPending,\n error,\n totalCount,\n offset,\n pageSize,\n hasNextPage,\n hasPreviousPage,\n onNextPage,\n onPreviousPage,\n onPageSizeChange,\n reload,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useCompletePagination.esm.js","sources":["../../../../src/components/Table/hooks/useCompletePagination.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport type { TableItem } from '../types';\nimport type {\n PaginationResult,\n QueryState,\n UseTableCompleteOptions,\n} from './types';\nimport { useStableCallback } from './useStableCallback';\nimport { useDebouncedValue } from './useDebouncedValue';\nimport { getEffectivePageSize } from './getEffectivePageSize';\n\n/** @internal */\nexport function useCompletePagination<T extends TableItem, TFilter>(\n options: UseTableCompleteOptions<T, TFilter>,\n query: QueryState<TFilter>,\n): PaginationResult<T> & { reload: () => void } {\n const {\n data,\n getData: getDataProp = () => [],\n paginationOptions = {},\n sortFn,\n filterFn,\n searchFn,\n searchDebounceMs = 0,\n filterDebounceMs = 0,\n } = options;\n const hasGetData = 'getData' in options;\n const noPagination = paginationOptions.type === 'none';\n const { initialOffset = 0 } = paginationOptions;\n const defaultPageSize = noPagination\n ? Infinity\n : getEffectivePageSize(paginationOptions);\n\n const getData = useStableCallback(getDataProp);\n const { sort, filter, search } = query;\n\n const [items, setItems] = useState<T[] | undefined>(undefined);\n const [isPending, setIsPending] = useState(!data);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [loadCount, setLoadCount] = useState(0);\n\n const [offset, setOffset] = useState(initialOffset);\n const [pageSize, setPageSize] = useState(defaultPageSize);\n\n // Sync pageSize when the caller changes paginationOptions.pageSize\n const previousDefaultPageSize = useRef(defaultPageSize);\n useEffect(() => {\n if (previousDefaultPageSize.current !== defaultPageSize) {\n previousDefaultPageSize.current = defaultPageSize;\n setPageSize(defaultPageSize);\n setOffset(0);\n }\n }, [defaultPageSize]);\n\n // Load data on mount and when loadCount changes (reload trigger)\n useEffect(() => {\n if (data) {\n setIsPending(false);\n return;\n }\n\n if (!hasGetData) {\n setIsPending(true);\n return;\n }\n\n let cancelled = false;\n setIsPending(true);\n setError(undefined);\n\n (async () => {\n try {\n const result = getData();\n const resolvedData = result instanceof Promise ? await result : result;\n if (!cancelled) {\n setItems(resolvedData);\n setIsPending(false);\n }\n } catch (err) {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsPending(false);\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [data, getData, hasGetData, loadCount]);\n\n // Debounced surrogates of search and filter feed the processing pipeline.\n // At delayMs === 0 (the default) these are referentially equal to the live\n // values, so behavior is identical to before this refactor.\n const debouncedSearch = useDebouncedValue(search, searchDebounceMs);\n const debouncedFilter = useDebouncedValue(filter, filterDebounceMs);\n\n // Reset offset when the *debounced* query changes — keying on the live query\n // would briefly flash page 1 of unfiltered data while the debounce settles.\n const debouncedQuery = useMemo(\n () => ({ sort, filter: debouncedFilter, search: debouncedSearch }),\n [sort, debouncedFilter, debouncedSearch],\n );\n const prevDebouncedQueryRef = useRef(debouncedQuery);\n useEffect(() => {\n if (prevDebouncedQueryRef.current !== debouncedQuery) {\n prevDebouncedQueryRef.current = debouncedQuery;\n setOffset(0);\n }\n }, [debouncedQuery]);\n\n const resolvedItems = useMemo(() => data ?? items, [data, items]);\n\n // Process data client-side (filter, search, sort)\n const processedData = useMemo(() => {\n if (!resolvedItems) {\n return undefined;\n }\n let result = [...resolvedItems];\n if (debouncedFilter !== undefined && filterFn) {\n result = filterFn(result, debouncedFilter);\n }\n if (debouncedSearch && searchFn) {\n result = searchFn(result, debouncedSearch);\n }\n if (sort && sortFn) {\n result = sortFn(result, sort);\n }\n return result;\n }, [\n resolvedItems,\n sort,\n debouncedFilter,\n debouncedSearch,\n filterFn,\n searchFn,\n sortFn,\n ]);\n\n const previousProcessedDataRef = useRef(processedData);\n if (processedData !== undefined) {\n previousProcessedDataRef.current = processedData;\n }\n const retainedProcessedData =\n processedData ?? previousProcessedDataRef.current;\n\n const totalCount = retainedProcessedData?.length ?? 0;\n\n const effectiveOffset = useMemo(() => {\n if (noPagination) {\n return 0;\n }\n if (retainedProcessedData === undefined) {\n return offset;\n }\n if (totalCount === 0) {\n return 0;\n }\n return Math.min(offset, Math.floor((totalCount - 1) / pageSize) * pageSize);\n }, [noPagination, offset, pageSize, retainedProcessedData, totalCount]);\n\n // Persist the corrected offset so later data growth does not restore it.\n useEffect(() => {\n if (offset !== effectiveOffset) {\n setOffset(effectiveOffset);\n }\n }, [effectiveOffset, offset]);\n\n // Paginate the processed data\n const paginatedData = useMemo(\n () =>\n noPagination\n ? retainedProcessedData\n : retainedProcessedData?.slice(\n effectiveOffset,\n effectiveOffset + pageSize,\n ),\n [retainedProcessedData, effectiveOffset, pageSize, noPagination],\n );\n\n const hasNextPage = !noPagination && effectiveOffset + pageSize < totalCount;\n const hasPreviousPage = !noPagination && effectiveOffset > 0;\n\n const onNextPage = useCallback(() => {\n if (effectiveOffset + pageSize < totalCount) {\n setOffset(effectiveOffset + pageSize);\n }\n }, [effectiveOffset, pageSize, totalCount]);\n\n const onPreviousPage = useCallback(() => {\n if (effectiveOffset > 0) {\n setOffset(Math.max(0, effectiveOffset - pageSize));\n }\n }, [effectiveOffset, pageSize]);\n\n const onPageSizeChange = useCallback((newSize: number) => {\n setPageSize(newSize);\n setOffset(0);\n }, []);\n\n const reload = useCallback(() => {\n setOffset(0);\n setLoadCount(c => c + 1);\n }, []);\n\n return {\n data: paginatedData,\n isPending: isPending,\n error,\n totalCount,\n offset: effectiveOffset,\n pageSize,\n hasNextPage,\n hasPreviousPage,\n onNextPage,\n onPreviousPage,\n onPageSizeChange,\n reload,\n };\n}\n"],"names":[],"mappings":";;;;;AA4BO,SAAS,qBAAA,CACd,SACA,KAAA,EAC8C;AAC9C,EAAA,MAAM;AAAA,IACJ,IAAA;AAAA,IACA,OAAA,EAAS,WAAA,GAAc,MAAM,EAAC;AAAA,IAC9B,oBAAoB,EAAC;AAAA,IACrB,MAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,gBAAA,GAAmB,CAAA;AAAA,IACnB,gBAAA,GAAmB;AAAA,GACrB,GAAI,OAAA;AACJ,EAAA,MAAM,aAAa,SAAA,IAAa,OAAA;AAChC,EAAA,MAAM,YAAA,GAAe,kBAAkB,IAAA,KAAS,MAAA;AAChD,EAAA,MAAM,EAAE,aAAA,GAAgB,CAAA,EAAE,GAAI,iBAAA;AAC9B,EAAA,MAAM,eAAA,GAAkB,YAAA,GACpB,QAAA,GACA,oBAAA,CAAqB,iBAAiB,CAAA;AAE1C,EAAA,MAAM,OAAA,GAAU,kBAAkB,WAAW,CAAA;AAC7C,EAAA,MAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAO,GAAI,KAAA;AAEjC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAA0B,MAAS,CAAA;AAC7D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,QAAA,CAAS,CAAC,IAAI,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAA4B,MAAS,CAAA;AAC/D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,CAAC,CAAA;AAE5C,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,aAAa,CAAA;AAClD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,eAAe,CAAA;AAGxD,EAAA,MAAM,uBAAA,GAA0B,OAAO,eAAe,CAAA;AACtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,uBAAA,CAAwB,YAAY,eAAA,EAAiB;AACvD,MAAA,uBAAA,CAAwB,OAAA,GAAU,eAAA;AAClC,MAAA,WAAA,CAAY,eAAe,CAAA;AAC3B,MAAA,SAAA,CAAU,CAAC,CAAA;AAAA,IACb;AAAA,EACF,CAAA,EAAG,CAAC,eAAe,CAAC,CAAA;AAGpB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,UAAA,EAAY;AACf,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,QAAA,CAAS,MAAS,CAAA;AAElB,IAAA,CAAC,YAAY;AACX,MAAA,IAAI;AACF,QAAA,MAAM,SAAS,OAAA,EAAQ;AACvB,QAAA,MAAM,YAAA,GAAe,MAAA,YAAkB,OAAA,GAAU,MAAM,MAAA,GAAS,MAAA;AAChE,QAAA,IAAI,CAAC,SAAA,EAAW;AACd,UAAA,QAAA,CAAS,YAAY,CAAA;AACrB,UAAA,YAAA,CAAa,KAAK,CAAA;AAAA,QACpB;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,CAAC,SAAA,EAAW;AACd,UAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAC5D,UAAA,YAAA,CAAa,KAAK,CAAA;AAAA,QACpB;AAAA,MACF;AAAA,IACF,CAAA,GAAG;AAEH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,GAAG,CAAC,IAAA,EAAM,OAAA,EAAS,UAAA,EAAY,SAAS,CAAC,CAAA;AAKzC,EAAA,MAAM,eAAA,GAAkB,iBAAA,CAAkB,MAAA,EAAQ,gBAAgB,CAAA;AAClE,EAAA,MAAM,eAAA,GAAkB,iBAAA,CAAkB,MAAA,EAAQ,gBAAgB,CAAA;AAIlE,EAAA,MAAM,cAAA,GAAiB,OAAA;AAAA,IACrB,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,eAAA,EAAiB,QAAQ,eAAA,EAAgB,CAAA;AAAA,IAChE,CAAC,IAAA,EAAM,eAAA,EAAiB,eAAe;AAAA,GACzC;AACA,EAAA,MAAM,qBAAA,GAAwB,OAAO,cAAc,CAAA;AACnD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,qBAAA,CAAsB,YAAY,cAAA,EAAgB;AACpD,MAAA,qBAAA,CAAsB,OAAA,GAAU,cAAA;AAChC,MAAA,SAAA,CAAU,CAAC,CAAA;AAAA,IACb;AAAA,EACF,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAEnB,EAAA,MAAM,aAAA,GAAgB,QAAQ,MAAM,IAAA,IAAQ,OAAO,CAAC,IAAA,EAAM,KAAK,CAAC,CAAA;AAGhE,EAAA,MAAM,aAAA,GAAgB,QAAQ,MAAM;AAClC,IAAA,IAAI,CAAC,aAAA,EAAe;AAClB,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,IAAI,MAAA,GAAS,CAAC,GAAG,aAAa,CAAA;AAC9B,IAAA,IAAI,eAAA,KAAoB,UAAa,QAAA,EAAU;AAC7C,MAAA,MAAA,GAAS,QAAA,CAAS,QAAQ,eAAe,CAAA;AAAA,IAC3C;AACA,IAAA,IAAI,mBAAmB,QAAA,EAAU;AAC/B,MAAA,MAAA,GAAS,QAAA,CAAS,QAAQ,eAAe,CAAA;AAAA,IAC3C;AACA,IAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,MAAA,MAAA,GAAS,MAAA,CAAO,QAAQ,IAAI,CAAA;AAAA,IAC9B;AACA,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,EAAG;AAAA,IACD,aAAA;AAAA,IACA,IAAA;AAAA,IACA,eAAA;AAAA,IACA,eAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,wBAAA,GAA2B,OAAO,aAAa,CAAA;AACrD,EAAA,IAAI,kBAAkB,MAAA,EAAW;AAC/B,IAAA,wBAAA,CAAyB,OAAA,GAAU,aAAA;AAAA,EACrC;AACA,EAAA,MAAM,qBAAA,GACJ,iBAAiB,wBAAA,CAAyB,OAAA;AAE5C,EAAA,MAAM,UAAA,GAAa,uBAAuB,MAAA,IAAU,CAAA;AAEpD,EAAA,MAAM,eAAA,GAAkB,QAAQ,MAAM;AACpC,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,OAAO,CAAA;AAAA,IACT;AACA,IAAA,IAAI,0BAA0B,MAAA,EAAW;AACvC,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,IAAI,eAAe,CAAA,EAAG;AACpB,MAAA,OAAO,CAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA,CAAK,IAAI,MAAA,EAAQ,IAAA,CAAK,OAAO,UAAA,GAAa,CAAA,IAAK,QAAQ,CAAA,GAAI,QAAQ,CAAA;AAAA,EAC5E,GAAG,CAAC,YAAA,EAAc,QAAQ,QAAA,EAAU,qBAAA,EAAuB,UAAU,CAAC,CAAA;AAGtE,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,WAAW,eAAA,EAAiB;AAC9B,MAAA,SAAA,CAAU,eAAe,CAAA;AAAA,IAC3B;AAAA,EACF,CAAA,EAAG,CAAC,eAAA,EAAiB,MAAM,CAAC,CAAA;AAG5B,EAAA,MAAM,aAAA,GAAgB,OAAA;AAAA,IACpB,MACE,YAAA,GACI,qBAAA,GACA,qBAAA,EAAuB,KAAA;AAAA,MACrB,eAAA;AAAA,MACA,eAAA,GAAkB;AAAA,KACpB;AAAA,IACN,CAAC,qBAAA,EAAuB,eAAA,EAAiB,QAAA,EAAU,YAAY;AAAA,GACjE;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,YAAA,IAAgB,eAAA,GAAkB,QAAA,GAAW,UAAA;AAClE,EAAA,MAAM,eAAA,GAAkB,CAAC,YAAA,IAAgB,eAAA,GAAkB,CAAA;AAE3D,EAAA,MAAM,UAAA,GAAa,YAAY,MAAM;AACnC,IAAA,IAAI,eAAA,GAAkB,WAAW,UAAA,EAAY;AAC3C,MAAA,SAAA,CAAU,kBAAkB,QAAQ,CAAA;AAAA,IACtC;AAAA,EACF,CAAA,EAAG,CAAC,eAAA,EAAiB,QAAA,EAAU,UAAU,CAAC,CAAA;AAE1C,EAAA,MAAM,cAAA,GAAiB,YAAY,MAAM;AACvC,IAAA,IAAI,kBAAkB,CAAA,EAAG;AACvB,MAAA,SAAA,CAAU,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,eAAA,GAAkB,QAAQ,CAAC,CAAA;AAAA,IACnD;AAAA,EACF,CAAA,EAAG,CAAC,eAAA,EAAiB,QAAQ,CAAC,CAAA;AAE9B,EAAA,MAAM,gBAAA,GAAmB,WAAA,CAAY,CAAC,OAAA,KAAoB;AACxD,IAAA,WAAA,CAAY,OAAO,CAAA;AACnB,IAAA,SAAA,CAAU,CAAC,CAAA;AAAA,EACb,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM;AAC/B,IAAA,SAAA,CAAU,CAAC,CAAA;AACX,IAAA,YAAA,CAAa,CAAA,CAAA,KAAK,IAAI,CAAC,CAAA;AAAA,EACzB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,aAAA;AAAA,IACN,SAAA;AAAA,IACA,KAAA;AAAA,IACA,UAAA;AAAA,IACA,MAAA,EAAQ,eAAA;AAAA,IACR,QAAA;AAAA,IACA,WAAA;AAAA,IACA,eAAA;AAAA,IACA,UAAA;AAAA,IACA,cAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { useRef, useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
function useDebouncedReload(query, pageSize, reload, delay = 200) {
|
|
4
|
-
const
|
|
4
|
+
const { sort, filter, search } = query;
|
|
5
|
+
const prevDepsRef = useRef({ sort, filter, search, pageSize });
|
|
5
6
|
useEffect(() => {
|
|
6
7
|
const prev = prevDepsRef.current;
|
|
7
|
-
if (prev.
|
|
8
|
-
prevDepsRef.current = {
|
|
8
|
+
if (prev.sort !== sort || prev.filter !== filter || prev.search !== search || prev.pageSize !== pageSize) {
|
|
9
|
+
prevDepsRef.current = { sort, filter, search, pageSize };
|
|
9
10
|
const timer = setTimeout(reload, delay);
|
|
10
11
|
return () => clearTimeout(timer);
|
|
11
12
|
}
|
|
12
13
|
return void 0;
|
|
13
|
-
}, [
|
|
14
|
+
}, [sort, filter, search, pageSize, reload, delay]);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export { useDebouncedReload };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDebouncedReload.esm.js","sources":["../../../../src/components/Table/hooks/useDebouncedReload.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useEffect, useRef } from 'react';\nimport type { QueryState } from './types';\n\n/**\n * Triggers a debounced reload when query or pageSize changes.\n * Debouncing reduces backend load during rapid changes (e.g., typing in search).\n */\n/** @internal */\nexport function useDebouncedReload<TFilter>(\n query: QueryState<TFilter>,\n pageSize: number,\n reload: () => void,\n delay: number = 200,\n): void {\n const prevDepsRef = useRef({
|
|
1
|
+
{"version":3,"file":"useDebouncedReload.esm.js","sources":["../../../../src/components/Table/hooks/useDebouncedReload.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useEffect, useRef } from 'react';\nimport type { QueryState } from './types';\n\n/**\n * Triggers a debounced reload when query or pageSize changes.\n * Debouncing reduces backend load during rapid changes (e.g., typing in search).\n */\n/** @internal */\nexport function useDebouncedReload<TFilter>(\n query: QueryState<TFilter>,\n pageSize: number,\n reload: () => void,\n delay: number = 200,\n): void {\n const { sort, filter, search } = query;\n const prevDepsRef = useRef({ sort, filter, search, pageSize });\n\n useEffect(() => {\n const prev = prevDepsRef.current;\n if (\n prev.sort !== sort ||\n prev.filter !== filter ||\n prev.search !== search ||\n prev.pageSize !== pageSize\n ) {\n prevDepsRef.current = { sort, filter, search, pageSize };\n const timer = setTimeout(reload, delay);\n return () => clearTimeout(timer);\n }\n return undefined;\n }, [sort, filter, search, pageSize, reload, delay]);\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,kBAAA,CACd,KAAA,EACA,QAAA,EACA,MAAA,EACA,QAAgB,GAAA,EACV;AACN,EAAA,MAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAO,GAAI,KAAA;AACjC,EAAA,MAAM,cAAc,MAAA,CAAO,EAAE,MAAM,MAAA,EAAQ,MAAA,EAAQ,UAAU,CAAA;AAE7D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,OAAO,WAAA,CAAY,OAAA;AACzB,IAAA,IACE,IAAA,CAAK,IAAA,KAAS,IAAA,IACd,IAAA,CAAK,MAAA,KAAW,MAAA,IAChB,IAAA,CAAK,MAAA,KAAW,MAAA,IAChB,IAAA,CAAK,QAAA,KAAa,QAAA,EAClB;AACA,MAAA,WAAA,CAAY,OAAA,GAAU,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAQ,QAAA,EAAS;AACvD,MAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,MAAA,EAAQ,KAAK,CAAA;AACtC,MAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,IACjC;AACA,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,EAAG,CAAC,IAAA,EAAM,MAAA,EAAQ,QAAQ,QAAA,EAAU,MAAA,EAAQ,KAAK,CAAC,CAAA;AACpD;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
1
|
+
import { useState, useRef, useReducer, useCallback, useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
const FIRST_PAGE_CURSOR = /* @__PURE__ */ Symbol("firstPage");
|
|
4
4
|
class PageCacheStore {
|
|
@@ -61,6 +61,7 @@ function usePageCache(options) {
|
|
|
61
61
|
const [isPending, setIsPending] = useState(true);
|
|
62
62
|
const [error, setError] = useState(void 0);
|
|
63
63
|
const [totalCount, setTotalCount] = useState(void 0);
|
|
64
|
+
const [, notifyCacheUpdate] = useReducer((version) => version + 1, 0);
|
|
64
65
|
const abortControllerRef = useRef(null);
|
|
65
66
|
const currentPage = cacheStore.get(currentCursor);
|
|
66
67
|
const data = currentPage?.data;
|
|
@@ -73,12 +74,13 @@ function usePageCache(options) {
|
|
|
73
74
|
currentCursor,
|
|
74
75
|
initialCurrentCursor
|
|
75
76
|
);
|
|
76
|
-
if (
|
|
77
|
+
if (targetCursor === void 0) {
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
80
|
const existingEntry = cacheStore.get(targetCursor);
|
|
80
81
|
if (existingEntry?.data !== void 0) {
|
|
81
82
|
setCurrentCursor(targetCursor);
|
|
83
|
+
setError(void 0);
|
|
82
84
|
return;
|
|
83
85
|
}
|
|
84
86
|
const entry = cacheStore.getOrCreate(targetCursor);
|
|
@@ -109,6 +111,7 @@ function usePageCache(options) {
|
|
|
109
111
|
if (result.totalCount !== void 0) {
|
|
110
112
|
setTotalCount(result.totalCount);
|
|
111
113
|
}
|
|
114
|
+
notifyCacheUpdate();
|
|
112
115
|
setIsPending(false);
|
|
113
116
|
} catch (err) {
|
|
114
117
|
if (abortController.signal.aborted) {
|
|
@@ -131,13 +134,13 @@ function usePageCache(options) {
|
|
|
131
134
|
const onNextPage = useCallback(() => {
|
|
132
135
|
if (isPending) return;
|
|
133
136
|
const page = cacheStore.get(currentCursor);
|
|
134
|
-
if (
|
|
137
|
+
if (page?.nextCursor === void 0) return;
|
|
135
138
|
goToPage("next");
|
|
136
139
|
}, [isPending, currentCursor, goToPage, cacheStore]);
|
|
137
140
|
const onPreviousPage = useCallback(() => {
|
|
138
141
|
if (isPending) return;
|
|
139
142
|
const page = cacheStore.get(currentCursor);
|
|
140
|
-
if (
|
|
143
|
+
if (page?.prevCursor === void 0) return;
|
|
141
144
|
goToPage("prev");
|
|
142
145
|
}, [isPending, currentCursor, goToPage, cacheStore]);
|
|
143
146
|
const reload = useCallback(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePageCache.esm.js","sources":["../../../../src/components/Table/hooks/usePageCache.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useState, useCallback, useRef, useEffect } from 'react';\n\nconst FIRST_PAGE_CURSOR = Symbol('firstPage');\n\ntype CursorType = string | number;\n\ntype InternalCursor<TCursor extends CursorType> =\n | TCursor\n | typeof FIRST_PAGE_CURSOR;\n\ninterface PageEntry<T, TCursor extends CursorType> {\n data: T[] | undefined;\n nextCursor: InternalCursor<TCursor> | undefined;\n prevCursor: InternalCursor<TCursor> | undefined;\n}\n\ninterface GetDataResult<T, TCursor extends CursorType> {\n data: T[];\n nextCursor?: TCursor;\n prevCursor?: TCursor;\n totalCount?: number;\n}\n\n/** @internal */\nexport interface UsePageCacheOptions<T, TCursor extends CursorType = string> {\n getData: (params: {\n cursor: TCursor | undefined;\n signal: AbortSignal;\n }) => Promise<GetDataResult<T, TCursor>>;\n initialCurrentCursor?: TCursor;\n}\n\n/** @internal */\nexport interface UsePageCacheResult<T, TCursor extends CursorType = string> {\n isPending: boolean;\n error: Error | undefined;\n data: T[] | undefined;\n totalCount: number | undefined;\n currentCursor: TCursor | undefined;\n hasPreviousPage: boolean;\n onPreviousPage: () => void;\n hasNextPage: boolean;\n onNextPage: () => void;\n reload: (options?: { keepCurrentCursor?: boolean }) => void;\n}\n\ntype Direction = 'mount' | 'reset' | 'refresh' | 'next' | 'prev';\n\nclass PageCacheStore<T, TCursor extends CursorType> {\n private cache = new Map<InternalCursor<TCursor>, PageEntry<T, TCursor>>();\n\n get(cursor: InternalCursor<TCursor>): PageEntry<T, TCursor> | undefined {\n return this.cache.get(cursor);\n }\n\n getOrCreate(cursor: InternalCursor<TCursor>): PageEntry<T, TCursor> {\n const existing = this.cache.get(cursor);\n if (existing) {\n return existing;\n }\n const entry: PageEntry<T, TCursor> = {\n data: undefined,\n nextCursor: undefined,\n prevCursor: undefined,\n };\n this.cache.set(cursor, entry);\n return entry;\n }\n\n clear() {\n this.cache.clear();\n }\n\n getTargetCursor(\n direction: Direction,\n currentCursor: InternalCursor<TCursor>,\n initialCurrentCursor: TCursor | undefined,\n ): InternalCursor<TCursor> | undefined {\n if (direction === 'mount') {\n return toInternalCursor(initialCurrentCursor);\n }\n if (direction === 'reset') {\n return FIRST_PAGE_CURSOR;\n }\n if (direction === 'refresh') {\n return currentCursor;\n }\n const currentEntry = this.cache.get(currentCursor);\n\n if (!currentEntry) {\n return;\n }\n\n return direction === 'next'\n ? currentEntry.nextCursor\n : currentEntry.prevCursor;\n }\n\n linkEntryToSource(\n entry: PageEntry<T, TCursor>,\n direction: Direction,\n currentCursor: InternalCursor<TCursor>,\n ) {\n if (direction === 'next') {\n entry.prevCursor = currentCursor;\n } else if (direction === 'prev') {\n entry.nextCursor = currentCursor;\n }\n }\n}\n\nfunction toInternalCursor<TCursor extends CursorType>(\n cursor: TCursor | undefined,\n): InternalCursor<TCursor> {\n return cursor === undefined ? FIRST_PAGE_CURSOR : cursor;\n}\n\nfunction toExternalCursor<TCursor extends CursorType>(\n cursor: InternalCursor<TCursor>,\n): TCursor | undefined {\n return cursor === FIRST_PAGE_CURSOR ? undefined : cursor;\n}\n\n/** @internal */\nexport function usePageCache<T, TCursor extends CursorType = string>(\n options: UsePageCacheOptions<T, TCursor>,\n): UsePageCacheResult<T, TCursor> {\n const { getData, initialCurrentCursor } = options;\n\n const [currentCursor, setCurrentCursor] = useState<InternalCursor<TCursor>>(\n () => toInternalCursor(initialCurrentCursor),\n );\n\n const cacheStore = useRef(new PageCacheStore<T, TCursor>()).current;\n\n const [isPending, setIsPending] = useState(true);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [totalCount, setTotalCount] = useState<number | undefined>(undefined);\n\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const currentPage = cacheStore.get(currentCursor);\n const data = currentPage?.data;\n const hasNextPage = currentPage?.nextCursor !== undefined;\n const hasPreviousPage = currentPage?.prevCursor !== undefined;\n\n const goToPage = useCallback(\n async (direction: Direction) => {\n const targetCursor = cacheStore.getTargetCursor(\n direction,\n currentCursor,\n initialCurrentCursor,\n );\n\n if (!targetCursor) {\n return;\n }\n\n const existingEntry = cacheStore.get(targetCursor);\n if (existingEntry?.data !== undefined) {\n setCurrentCursor(targetCursor);\n return;\n }\n\n const entry = cacheStore.getOrCreate(targetCursor);\n cacheStore.linkEntryToSource(entry, direction, currentCursor);\n setCurrentCursor(targetCursor);\n\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n setIsPending(true);\n setError(undefined);\n\n try {\n const result = await getData({\n cursor: toExternalCursor(targetCursor),\n signal: abortController.signal,\n });\n\n if (abortController.signal.aborted) {\n return;\n }\n\n entry.data = result.data;\n\n if (entry.nextCursor === undefined && result.nextCursor !== undefined) {\n entry.nextCursor = result.nextCursor;\n }\n if (entry.prevCursor === undefined && result.prevCursor !== undefined) {\n entry.prevCursor = result.prevCursor;\n }\n\n if (result.totalCount !== undefined) {\n setTotalCount(result.totalCount);\n }\n\n setIsPending(false);\n } catch (err) {\n if (abortController.signal.aborted) {\n return;\n }\n\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsPending(false);\n }\n },\n [getData, initialCurrentCursor, currentCursor, cacheStore],\n );\n\n useEffect(() => {\n goToPage('mount');\n\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n }, []);\n\n const onNextPage = useCallback(() => {\n if (isPending) return;\n const page = cacheStore.get(currentCursor);\n if (!page?.nextCursor) return;\n goToPage('next');\n }, [isPending, currentCursor, goToPage, cacheStore]);\n\n const onPreviousPage = useCallback(() => {\n if (isPending) return;\n const page = cacheStore.get(currentCursor);\n if (!page?.prevCursor) return;\n goToPage('prev');\n }, [isPending, currentCursor, goToPage, cacheStore]);\n\n const reload = useCallback(\n (reloadOptions?: { keepCurrentCursor?: boolean }) => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n cacheStore.clear();\n\n goToPage(reloadOptions?.keepCurrentCursor ? 'refresh' : 'reset');\n },\n [goToPage, cacheStore],\n );\n\n return {\n isPending,\n error,\n data,\n totalCount,\n currentCursor: toExternalCursor(currentCursor),\n hasPreviousPage,\n onPreviousPage,\n hasNextPage,\n onNextPage,\n reload,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"usePageCache.esm.js","sources":["../../../../src/components/Table/hooks/usePageCache.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useState, useCallback, useRef, useEffect, useReducer } from 'react';\n\nconst FIRST_PAGE_CURSOR = Symbol('firstPage');\n\ntype CursorType = string | number;\n\ntype InternalCursor<TCursor extends CursorType> =\n | TCursor\n | typeof FIRST_PAGE_CURSOR;\n\ninterface PageEntry<T, TCursor extends CursorType> {\n data: T[] | undefined;\n nextCursor: InternalCursor<TCursor> | undefined;\n prevCursor: InternalCursor<TCursor> | undefined;\n}\n\ninterface GetDataResult<T, TCursor extends CursorType> {\n data: T[];\n nextCursor?: TCursor;\n prevCursor?: TCursor;\n totalCount?: number;\n}\n\n/** @internal */\nexport interface UsePageCacheOptions<T, TCursor extends CursorType = string> {\n getData: (params: {\n cursor: TCursor | undefined;\n signal: AbortSignal;\n }) => Promise<GetDataResult<T, TCursor>>;\n initialCurrentCursor?: TCursor;\n}\n\n/** @internal */\nexport interface UsePageCacheResult<T, TCursor extends CursorType = string> {\n isPending: boolean;\n error: Error | undefined;\n data: T[] | undefined;\n totalCount: number | undefined;\n currentCursor: TCursor | undefined;\n hasPreviousPage: boolean;\n onPreviousPage: () => void;\n hasNextPage: boolean;\n onNextPage: () => void;\n reload: (options?: { keepCurrentCursor?: boolean }) => void;\n}\n\ntype Direction = 'mount' | 'reset' | 'refresh' | 'next' | 'prev';\n\nclass PageCacheStore<T, TCursor extends CursorType> {\n private cache = new Map<InternalCursor<TCursor>, PageEntry<T, TCursor>>();\n\n get(cursor: InternalCursor<TCursor>): PageEntry<T, TCursor> | undefined {\n return this.cache.get(cursor);\n }\n\n getOrCreate(cursor: InternalCursor<TCursor>): PageEntry<T, TCursor> {\n const existing = this.cache.get(cursor);\n if (existing) {\n return existing;\n }\n const entry: PageEntry<T, TCursor> = {\n data: undefined,\n nextCursor: undefined,\n prevCursor: undefined,\n };\n this.cache.set(cursor, entry);\n return entry;\n }\n\n clear() {\n this.cache.clear();\n }\n\n getTargetCursor(\n direction: Direction,\n currentCursor: InternalCursor<TCursor>,\n initialCurrentCursor: TCursor | undefined,\n ): InternalCursor<TCursor> | undefined {\n if (direction === 'mount') {\n return toInternalCursor(initialCurrentCursor);\n }\n if (direction === 'reset') {\n return FIRST_PAGE_CURSOR;\n }\n if (direction === 'refresh') {\n return currentCursor;\n }\n const currentEntry = this.cache.get(currentCursor);\n\n if (!currentEntry) {\n return;\n }\n\n return direction === 'next'\n ? currentEntry.nextCursor\n : currentEntry.prevCursor;\n }\n\n linkEntryToSource(\n entry: PageEntry<T, TCursor>,\n direction: Direction,\n currentCursor: InternalCursor<TCursor>,\n ) {\n if (direction === 'next') {\n entry.prevCursor = currentCursor;\n } else if (direction === 'prev') {\n entry.nextCursor = currentCursor;\n }\n }\n}\n\nfunction toInternalCursor<TCursor extends CursorType>(\n cursor: TCursor | undefined,\n): InternalCursor<TCursor> {\n return cursor === undefined ? FIRST_PAGE_CURSOR : cursor;\n}\n\nfunction toExternalCursor<TCursor extends CursorType>(\n cursor: InternalCursor<TCursor>,\n): TCursor | undefined {\n return cursor === FIRST_PAGE_CURSOR ? undefined : cursor;\n}\n\n/** @internal */\nexport function usePageCache<T, TCursor extends CursorType = string>(\n options: UsePageCacheOptions<T, TCursor>,\n): UsePageCacheResult<T, TCursor> {\n const { getData, initialCurrentCursor } = options;\n\n const [currentCursor, setCurrentCursor] = useState<InternalCursor<TCursor>>(\n () => toInternalCursor(initialCurrentCursor),\n );\n\n const cacheStore = useRef(new PageCacheStore<T, TCursor>()).current;\n\n const [isPending, setIsPending] = useState(true);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [totalCount, setTotalCount] = useState<number | undefined>(undefined);\n const [, notifyCacheUpdate] = useReducer(version => version + 1, 0);\n\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const currentPage = cacheStore.get(currentCursor);\n const data = currentPage?.data;\n const hasNextPage = currentPage?.nextCursor !== undefined;\n const hasPreviousPage = currentPage?.prevCursor !== undefined;\n\n const goToPage = useCallback(\n async (direction: Direction) => {\n const targetCursor = cacheStore.getTargetCursor(\n direction,\n currentCursor,\n initialCurrentCursor,\n );\n\n if (targetCursor === undefined) {\n return;\n }\n\n const existingEntry = cacheStore.get(targetCursor);\n if (existingEntry?.data !== undefined) {\n setCurrentCursor(targetCursor);\n setError(undefined);\n return;\n }\n\n const entry = cacheStore.getOrCreate(targetCursor);\n cacheStore.linkEntryToSource(entry, direction, currentCursor);\n setCurrentCursor(targetCursor);\n\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n setIsPending(true);\n setError(undefined);\n\n try {\n const result = await getData({\n cursor: toExternalCursor(targetCursor),\n signal: abortController.signal,\n });\n\n if (abortController.signal.aborted) {\n return;\n }\n\n entry.data = result.data;\n\n if (entry.nextCursor === undefined && result.nextCursor !== undefined) {\n entry.nextCursor = result.nextCursor;\n }\n if (entry.prevCursor === undefined && result.prevCursor !== undefined) {\n entry.prevCursor = result.prevCursor;\n }\n\n if (result.totalCount !== undefined) {\n setTotalCount(result.totalCount);\n }\n\n notifyCacheUpdate();\n setIsPending(false);\n } catch (err) {\n if (abortController.signal.aborted) {\n return;\n }\n\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsPending(false);\n }\n },\n [getData, initialCurrentCursor, currentCursor, cacheStore],\n );\n\n useEffect(() => {\n goToPage('mount');\n\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n }, []);\n\n const onNextPage = useCallback(() => {\n if (isPending) return;\n const page = cacheStore.get(currentCursor);\n if (page?.nextCursor === undefined) return;\n goToPage('next');\n }, [isPending, currentCursor, goToPage, cacheStore]);\n\n const onPreviousPage = useCallback(() => {\n if (isPending) return;\n const page = cacheStore.get(currentCursor);\n if (page?.prevCursor === undefined) return;\n goToPage('prev');\n }, [isPending, currentCursor, goToPage, cacheStore]);\n\n const reload = useCallback(\n (reloadOptions?: { keepCurrentCursor?: boolean }) => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n cacheStore.clear();\n\n goToPage(reloadOptions?.keepCurrentCursor ? 'refresh' : 'reset');\n },\n [goToPage, cacheStore],\n );\n\n return {\n isPending,\n error,\n data,\n totalCount,\n currentCursor: toExternalCursor(currentCursor),\n hasPreviousPage,\n onPreviousPage,\n hasNextPage,\n onNextPage,\n reload,\n };\n}\n"],"names":[],"mappings":";;AAkBA,MAAM,iBAAA,0BAA2B,WAAW,CAAA;AA8C5C,MAAM,cAAA,CAA8C;AAAA,EAC1C,KAAA,uBAAY,GAAA,EAAoD;AAAA,EAExE,IAAI,MAAA,EAAoE;AACtE,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA;AAAA,EAC9B;AAAA,EAEA,YAAY,MAAA,EAAwD;AAClE,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA;AACtC,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,OAAO,QAAA;AAAA,IACT;AACA,IAAA,MAAM,KAAA,GAA+B;AAAA,MACnC,IAAA,EAAM,MAAA;AAAA,MACN,UAAA,EAAY,MAAA;AAAA,MACZ,UAAA,EAAY;AAAA,KACd;AACA,IAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAA,EAAQ,KAAK,CAAA;AAC5B,IAAA,OAAO,KAAA;AAAA,EACT;AAAA,EAEA,KAAA,GAAQ;AACN,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AAAA,EACnB;AAAA,EAEA,eAAA,CACE,SAAA,EACA,aAAA,EACA,oBAAA,EACqC;AACrC,IAAA,IAAI,cAAc,OAAA,EAAS;AACzB,MAAA,OAAO,iBAAiB,oBAAoB,CAAA;AAAA,IAC9C;AACA,IAAA,IAAI,cAAc,OAAA,EAAS;AACzB,MAAA,OAAO,iBAAA;AAAA,IACT;AACA,IAAA,IAAI,cAAc,SAAA,EAAW;AAC3B,MAAA,OAAO,aAAA;AAAA,IACT;AACA,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,aAAa,CAAA;AAEjD,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA;AAAA,IACF;AAEA,IAAA,OAAO,SAAA,KAAc,MAAA,GACjB,YAAA,CAAa,UAAA,GACb,YAAA,CAAa,UAAA;AAAA,EACnB;AAAA,EAEA,iBAAA,CACE,KAAA,EACA,SAAA,EACA,aAAA,EACA;AACA,IAAA,IAAI,cAAc,MAAA,EAAQ;AACxB,MAAA,KAAA,CAAM,UAAA,GAAa,aAAA;AAAA,IACrB,CAAA,MAAA,IAAW,cAAc,MAAA,EAAQ;AAC/B,MAAA,KAAA,CAAM,UAAA,GAAa,aAAA;AAAA,IACrB;AAAA,EACF;AACF;AAEA,SAAS,iBACP,MAAA,EACyB;AACzB,EAAA,OAAO,MAAA,KAAW,SAAY,iBAAA,GAAoB,MAAA;AACpD;AAEA,SAAS,iBACP,MAAA,EACqB;AACrB,EAAA,OAAO,MAAA,KAAW,oBAAoB,MAAA,GAAY,MAAA;AACpD;AAGO,SAAS,aACd,OAAA,EACgC;AAChC,EAAA,MAAM,EAAE,OAAA,EAAS,oBAAA,EAAqB,GAAI,OAAA;AAE1C,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,QAAA;AAAA,IACxC,MAAM,iBAAiB,oBAAoB;AAAA,GAC7C;AAEA,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,IAAI,cAAA,EAA4B,CAAA,CAAE,OAAA;AAE5D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,IAAI,CAAA;AAC/C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAA4B,MAAS,CAAA;AAC/D,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAA6B,MAAS,CAAA;AAC1E,EAAA,MAAM,GAAG,iBAAiB,CAAA,GAAI,WAAW,CAAA,OAAA,KAAW,OAAA,GAAU,GAAG,CAAC,CAAA;AAElE,EAAA,MAAM,kBAAA,GAAqB,OAA+B,IAAI,CAAA;AAE9D,EAAA,MAAM,WAAA,GAAc,UAAA,CAAW,GAAA,CAAI,aAAa,CAAA;AAChD,EAAA,MAAM,OAAO,WAAA,EAAa,IAAA;AAC1B,EAAA,MAAM,WAAA,GAAc,aAAa,UAAA,KAAe,MAAA;AAChD,EAAA,MAAM,eAAA,GAAkB,aAAa,UAAA,KAAe,MAAA;AAEpD,EAAA,MAAM,QAAA,GAAW,WAAA;AAAA,IACf,OAAO,SAAA,KAAyB;AAC9B,MAAA,MAAM,eAAe,UAAA,CAAW,eAAA;AAAA,QAC9B,SAAA;AAAA,QACA,aAAA;AAAA,QACA;AAAA,OACF;AAEA,MAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,aAAA,GAAgB,UAAA,CAAW,GAAA,CAAI,YAAY,CAAA;AACjD,MAAA,IAAI,aAAA,EAAe,SAAS,MAAA,EAAW;AACrC,QAAA,gBAAA,CAAiB,YAAY,CAAA;AAC7B,QAAA,QAAA,CAAS,MAAS,CAAA;AAClB,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,WAAA,CAAY,YAAY,CAAA;AACjD,MAAA,UAAA,CAAW,iBAAA,CAAkB,KAAA,EAAO,SAAA,EAAW,aAAa,CAAA;AAC5D,MAAA,gBAAA,CAAiB,YAAY,CAAA;AAE7B,MAAA,IAAI,mBAAmB,OAAA,EAAS;AAC9B,QAAA,kBAAA,CAAmB,QAAQ,KAAA,EAAM;AAAA,MACnC;AAEA,MAAA,MAAM,eAAA,GAAkB,IAAI,eAAA,EAAgB;AAC5C,MAAA,kBAAA,CAAmB,OAAA,GAAU,eAAA;AAE7B,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,MAAS,CAAA;AAElB,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ;AAAA,UAC3B,MAAA,EAAQ,iBAAiB,YAAY,CAAA;AAAA,UACrC,QAAQ,eAAA,CAAgB;AAAA,SACzB,CAAA;AAED,QAAA,IAAI,eAAA,CAAgB,OAAO,OAAA,EAAS;AAClC,UAAA;AAAA,QACF;AAEA,QAAA,KAAA,CAAM,OAAO,MAAA,CAAO,IAAA;AAEpB,QAAA,IAAI,KAAA,CAAM,UAAA,KAAe,KAAA,CAAA,IAAa,MAAA,CAAO,eAAe,KAAA,CAAA,EAAW;AACrE,UAAA,KAAA,CAAM,aAAa,MAAA,CAAO,UAAA;AAAA,QAC5B;AACA,QAAA,IAAI,KAAA,CAAM,UAAA,KAAe,KAAA,CAAA,IAAa,MAAA,CAAO,eAAe,KAAA,CAAA,EAAW;AACrE,UAAA,KAAA,CAAM,aAAa,MAAA,CAAO,UAAA;AAAA,QAC5B;AAEA,QAAA,IAAI,MAAA,CAAO,eAAe,KAAA,CAAA,EAAW;AACnC,UAAA,aAAA,CAAc,OAAO,UAAU,CAAA;AAAA,QACjC;AAEA,QAAA,iBAAA,EAAkB;AAClB,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,eAAA,CAAgB,OAAO,OAAA,EAAS;AAClC,UAAA;AAAA,QACF;AAEA,QAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAC5D,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,OAAA,EAAS,oBAAA,EAAsB,aAAA,EAAe,UAAU;AAAA,GAC3D;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,QAAA,CAAS,OAAO,CAAA;AAEhB,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,mBAAmB,OAAA,EAAS;AAC9B,QAAA,kBAAA,CAAmB,QAAQ,KAAA,EAAM;AAAA,MACnC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAa,YAAY,MAAM;AACnC,IAAA,IAAI,SAAA,EAAW;AACf,IAAA,MAAM,IAAA,GAAO,UAAA,CAAW,GAAA,CAAI,aAAa,CAAA;AACzC,IAAA,IAAI,IAAA,EAAM,eAAe,MAAA,EAAW;AACpC,IAAA,QAAA,CAAS,MAAM,CAAA;AAAA,EACjB,GAAG,CAAC,SAAA,EAAW,aAAA,EAAe,QAAA,EAAU,UAAU,CAAC,CAAA;AAEnD,EAAA,MAAM,cAAA,GAAiB,YAAY,MAAM;AACvC,IAAA,IAAI,SAAA,EAAW;AACf,IAAA,MAAM,IAAA,GAAO,UAAA,CAAW,GAAA,CAAI,aAAa,CAAA;AACzC,IAAA,IAAI,IAAA,EAAM,eAAe,MAAA,EAAW;AACpC,IAAA,QAAA,CAAS,MAAM,CAAA;AAAA,EACjB,GAAG,CAAC,SAAA,EAAW,aAAA,EAAe,QAAA,EAAU,UAAU,CAAC,CAAA;AAEnD,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,CAAC,aAAA,KAAoD;AACnD,MAAA,IAAI,mBAAmB,OAAA,EAAS;AAC9B,QAAA,kBAAA,CAAmB,QAAQ,KAAA,EAAM;AAAA,MACnC;AAEA,MAAA,UAAA,CAAW,KAAA,EAAM;AAEjB,MAAA,QAAA,CAAS,aAAA,EAAe,iBAAA,GAAoB,SAAA,GAAY,OAAO,CAAA;AAAA,IACjE,CAAA;AAAA,IACA,CAAC,UAAU,UAAU;AAAA,GACvB;AAEA,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA,UAAA;AAAA,IACA,aAAA,EAAe,iBAAiB,aAAa,CAAA;AAAA,IAC7C,eAAA;AAAA,IACA,cAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
|
|
@@ -54,6 +54,9 @@ function TablePagination(props) {
|
|
|
54
54
|
[pageSizeOptions]
|
|
55
55
|
);
|
|
56
56
|
const effectivePageSize = useMemo(() => {
|
|
57
|
+
if (pageSizeOptions.length === 0) {
|
|
58
|
+
return pageSize;
|
|
59
|
+
}
|
|
57
60
|
const isValid = pageSizeOptions.some(
|
|
58
61
|
(opt) => getOptionValue(opt) === pageSize
|
|
59
62
|
);
|
|
@@ -77,7 +80,7 @@ function TablePagination(props) {
|
|
|
77
80
|
label = `${fromCount} - ${toCount} of ${totalCount}`;
|
|
78
81
|
}
|
|
79
82
|
return /* @__PURE__ */ jsxs("div", { className: classes.root, children: [
|
|
80
|
-
/* @__PURE__ */ jsx("div", { className: classes.left, children: showPageSizeOptions && /* @__PURE__ */ jsx(
|
|
83
|
+
/* @__PURE__ */ jsx("div", { className: classes.left, children: showPageSizeOptions && normalizedOptions.length > 0 && /* @__PURE__ */ jsx(
|
|
81
84
|
Select,
|
|
82
85
|
{
|
|
83
86
|
name: "pageSize",
|
|
@@ -87,7 +90,7 @@ function TablePagination(props) {
|
|
|
87
90
|
label: opt.label,
|
|
88
91
|
value: String(opt.value)
|
|
89
92
|
})),
|
|
90
|
-
|
|
93
|
+
value: effectivePageSize.toString(),
|
|
91
94
|
onChange: (value) => {
|
|
92
95
|
const newPageSize = Number(value);
|
|
93
96
|
onPageSizeChange?.(newPageSize);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TablePagination.esm.js","sources":["../../../src/components/TablePagination/TablePagination.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useId } from 'react-aria';\nimport { Text } from '../Text';\nimport { ButtonIcon } from '../ButtonIcon';\nimport { Select } from '../Select';\nimport type { TablePaginationProps, PageSizeOption } from './types';\nimport { useDefinition } from '../../hooks/useDefinition';\nimport { TablePaginationDefinition } from './definition';\nimport { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react';\nimport { useMemo } from 'react';\n\nfunction getOptionValue(option: number | PageSizeOption): number {\n return typeof option === 'number' ? option : option.value;\n}\n\nfunction isNumberArray(\n options: number[] | PageSizeOption[],\n): options is number[] {\n return options.length > 0 && typeof options[0] === 'number';\n}\n\nfunction normalizePageSizeOptions(\n options: number[] | PageSizeOption[],\n): PageSizeOption[] {\n if (isNumberArray(options)) {\n return options.map(value => ({\n label: `Show ${value} results`,\n value,\n }));\n }\n return options;\n}\n\n/**\n * Pagination controls for Table components with page navigation and size selection.\n *\n * @public\n */\nexport function TablePagination(props: TablePaginationProps) {\n const { ownProps } = useDefinition(TablePaginationDefinition, props);\n const {\n classes,\n pageSize,\n pageSizeOptions,\n offset,\n totalCount,\n hasNextPage,\n hasPreviousPage,\n onNextPage,\n onPreviousPage,\n onPageSizeChange,\n showPageSizeOptions,\n getLabel,\n showPaginationLabel,\n } = ownProps;\n\n const labelId = useId();\n const normalizedOptions = useMemo(\n () => normalizePageSizeOptions(pageSizeOptions),\n [pageSizeOptions],\n );\n\n const effectivePageSize = useMemo(() => {\n const isValid = pageSizeOptions.some(\n opt => getOptionValue(opt) === pageSize,\n );\n if (isValid) {\n return pageSize;\n }\n const firstValue = getOptionValue(pageSizeOptions[0]);\n console.warn(\n `TablePagination: pageSize ${pageSize} is not in pageSizeOptions, using ${firstValue} instead`,\n );\n return firstValue;\n }, [pageSize, pageSizeOptions]);\n\n const hasItems = totalCount !== undefined && totalCount !== 0;\n\n const showLabel = hasItems && showPaginationLabel !== false;\n\n let label = `${totalCount} items`;\n if (getLabel) {\n label = getLabel({ pageSize: effectivePageSize, offset, totalCount });\n } else if (offset !== undefined) {\n const fromCount = offset + 1;\n const toCount = Math.min(offset + effectivePageSize, totalCount ?? 0);\n label = `${fromCount} - ${toCount} of ${totalCount}`;\n }\n\n return (\n <div className={classes.root}>\n <div className={classes.left}>\n {showPageSizeOptions && (\n <Select\n name=\"pageSize\"\n size=\"small\"\n aria-label=\"Select table page size\"\n options={normalizedOptions.map(opt => ({\n label: opt.label,\n value: String(opt.value),\n }))}\n
|
|
1
|
+
{"version":3,"file":"TablePagination.esm.js","sources":["../../../src/components/TablePagination/TablePagination.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useId } from 'react-aria';\nimport { Text } from '../Text';\nimport { ButtonIcon } from '../ButtonIcon';\nimport { Select } from '../Select';\nimport type { TablePaginationProps, PageSizeOption } from './types';\nimport { useDefinition } from '../../hooks/useDefinition';\nimport { TablePaginationDefinition } from './definition';\nimport { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react';\nimport { useMemo } from 'react';\n\nfunction getOptionValue(option: number | PageSizeOption): number {\n return typeof option === 'number' ? option : option.value;\n}\n\nfunction isNumberArray(\n options: number[] | PageSizeOption[],\n): options is number[] {\n return options.length > 0 && typeof options[0] === 'number';\n}\n\nfunction normalizePageSizeOptions(\n options: number[] | PageSizeOption[],\n): PageSizeOption[] {\n if (isNumberArray(options)) {\n return options.map(value => ({\n label: `Show ${value} results`,\n value,\n }));\n }\n return options;\n}\n\n/**\n * Pagination controls for Table components with page navigation and size selection.\n *\n * @public\n */\nexport function TablePagination(props: TablePaginationProps) {\n const { ownProps } = useDefinition(TablePaginationDefinition, props);\n const {\n classes,\n pageSize,\n pageSizeOptions,\n offset,\n totalCount,\n hasNextPage,\n hasPreviousPage,\n onNextPage,\n onPreviousPage,\n onPageSizeChange,\n showPageSizeOptions,\n getLabel,\n showPaginationLabel,\n } = ownProps;\n\n const labelId = useId();\n const normalizedOptions = useMemo(\n () => normalizePageSizeOptions(pageSizeOptions),\n [pageSizeOptions],\n );\n\n const effectivePageSize = useMemo(() => {\n if (pageSizeOptions.length === 0) {\n return pageSize;\n }\n const isValid = pageSizeOptions.some(\n opt => getOptionValue(opt) === pageSize,\n );\n if (isValid) {\n return pageSize;\n }\n const firstValue = getOptionValue(pageSizeOptions[0]);\n console.warn(\n `TablePagination: pageSize ${pageSize} is not in pageSizeOptions, using ${firstValue} instead`,\n );\n return firstValue;\n }, [pageSize, pageSizeOptions]);\n\n const hasItems = totalCount !== undefined && totalCount !== 0;\n\n const showLabel = hasItems && showPaginationLabel !== false;\n\n let label = `${totalCount} items`;\n if (getLabel) {\n label = getLabel({ pageSize: effectivePageSize, offset, totalCount });\n } else if (offset !== undefined) {\n const fromCount = offset + 1;\n const toCount = Math.min(offset + effectivePageSize, totalCount ?? 0);\n label = `${fromCount} - ${toCount} of ${totalCount}`;\n }\n\n return (\n <div className={classes.root}>\n <div className={classes.left}>\n {showPageSizeOptions && normalizedOptions.length > 0 && (\n <Select\n name=\"pageSize\"\n size=\"small\"\n aria-label=\"Select table page size\"\n options={normalizedOptions.map(opt => ({\n label: opt.label,\n value: String(opt.value),\n }))}\n value={effectivePageSize.toString()}\n onChange={value => {\n const newPageSize = Number(value);\n onPageSizeChange?.(newPageSize);\n }}\n className={classes.select}\n />\n )}\n </div>\n <div className={classes.right}>\n {showLabel && (\n <Text as=\"p\" variant=\"body-medium\" id={labelId}>\n {label}\n </Text>\n )}\n <ButtonIcon\n variant=\"secondary\"\n size=\"small\"\n onClick={onPreviousPage}\n isDisabled={!hasPreviousPage}\n icon={<RiArrowLeftSLine />}\n aria-label=\"Previous table page\"\n aria-describedby={showLabel ? labelId : undefined}\n />\n <ButtonIcon\n variant=\"secondary\"\n size=\"small\"\n onClick={onNextPage}\n isDisabled={!hasNextPage}\n icon={<RiArrowRightSLine />}\n aria-label=\"Next table page\"\n aria-describedby={showLabel ? labelId : undefined}\n />\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1934,7 +1934,7 @@ declare const HeaderMetadataUsers: ({ users, }: {
|
|
|
1934
1934
|
}) => react_jsx_runtime.JSX.Element | null;
|
|
1935
1935
|
|
|
1936
1936
|
/**
|
|
1937
|
-
* Displays a single status indicator as a
|
|
1937
|
+
* Displays a single status indicator as a colored dot with a label inside a
|
|
1938
1938
|
* Header metadata value. Optionally renders the label as a link when href is provided.
|
|
1939
1939
|
*
|
|
1940
1940
|
* @public
|
|
@@ -2750,7 +2750,9 @@ interface PageSizeOption {
|
|
|
2750
2750
|
}
|
|
2751
2751
|
/** @public */
|
|
2752
2752
|
type TablePaginationOwnProps = {
|
|
2753
|
+
/** The controlled number of rows displayed per page. */
|
|
2753
2754
|
pageSize: number;
|
|
2755
|
+
/** Available page sizes. An empty array hides the page-size selector. */
|
|
2754
2756
|
pageSizeOptions?: number[] | PageSizeOption[];
|
|
2755
2757
|
offset?: number;
|
|
2756
2758
|
totalCount?: number;
|
|
@@ -3143,6 +3145,12 @@ interface QueryOptions<TFilter> {
|
|
|
3143
3145
|
}
|
|
3144
3146
|
/** @public */
|
|
3145
3147
|
interface PaginationOptions extends Partial<Pick<TablePaginationProps, 'pageSize' | 'pageSizeOptions' | 'onPageSizeChange' | 'onNextPage' | 'onPreviousPage' | 'showPageSizeOptions' | 'getLabel' | 'showPaginationLabel'>> {
|
|
3148
|
+
/**
|
|
3149
|
+
* The zero-based item offset to use for the initial page.
|
|
3150
|
+
*
|
|
3151
|
+
* In complete mode, the offset is adjusted to the last available page if
|
|
3152
|
+
* the data later becomes too short for the current page.
|
|
3153
|
+
*/
|
|
3146
3154
|
initialOffset?: number;
|
|
3147
3155
|
}
|
|
3148
3156
|
/** @public */
|
|
@@ -3199,6 +3207,11 @@ type UseTableCompleteOptions<T extends TableItem, TFilter = unknown> = QueryOpti
|
|
|
3199
3207
|
*/
|
|
3200
3208
|
filterDebounceMs?: number;
|
|
3201
3209
|
} & ({
|
|
3210
|
+
/**
|
|
3211
|
+
* Controlled table data. Set to `undefined` while loading new data;
|
|
3212
|
+
* the previous data and pagination metadata remain visible and are
|
|
3213
|
+
* reported as stale.
|
|
3214
|
+
*/
|
|
3202
3215
|
data: T[] | undefined;
|
|
3203
3216
|
getData?: never;
|
|
3204
3217
|
} | {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/ui",
|
|
3
|
-
"version": "0.18.0
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "web-library"
|
|
6
6
|
},
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"test": "backstage-cli package test"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@backstage/version-bridge": "1.0.12",
|
|
53
|
+
"@backstage/version-bridge": "^1.0.12",
|
|
54
54
|
"@braintree/sanitize-url": "^7.1.2",
|
|
55
55
|
"@internationalized/date": "^3.12.0",
|
|
56
56
|
"@remixicon/react": ">=4.6.0 <4.9.0",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"use-sync-external-store": "^1.4.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@backstage/cli": "0.36.6
|
|
67
|
-
"@backstage/core-components": "0.18.14
|
|
66
|
+
"@backstage/cli": "^0.36.6",
|
|
67
|
+
"@backstage/core-components": "^0.18.14",
|
|
68
68
|
"@storybook/react-vite": "^10.4.0",
|
|
69
69
|
"@testing-library/dom": "^10.0.0",
|
|
70
70
|
"@testing-library/jest-dom": "^6.0.0",
|