@canvas-components/react-components 0.3.28 → 0.3.29

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/README.md CHANGED
@@ -75,7 +75,10 @@ export function ProfileForm() {
75
75
  <form onSubmit={(event) => event.preventDefault()}>
76
76
  <label>
77
77
  姓名
78
- <ReactInput value={name} onChange={(event) => setName(event.currentTarget.value)} />
78
+ <ReactInput
79
+ value={name}
80
+ onChange={(event) => setName(event.currentTarget.value)}
81
+ />
79
82
  </label>
80
83
  <label>
81
84
  团队
@@ -97,7 +100,9 @@ export function ProfileForm() {
97
100
  接收团队动态
98
101
  </ReactCheckbox>
99
102
  <ReactFlex gap={8}>
100
- <ReactButton type="primary" htmlType="submit">保存</ReactButton>
103
+ <ReactButton type="primary" htmlType="submit">
104
+ 保存
105
+ </ReactButton>
101
106
  <ReactPopconfirm title="确认删除?" onConfirm={() => setName("")}>
102
107
  <ReactButton danger>删除</ReactButton>
103
108
  </ReactPopconfirm>
@@ -112,7 +117,10 @@ export function ProfileForm() {
112
117
  `ReactSelect` 的暗色模式通过 `ReactConfigProvider` 配置。输入框与挂载到页面外层的下拉面板会使用同一组颜色:
113
118
 
114
119
  ```tsx
115
- import { ReactConfigProvider, ReactSelect } from "@canvas-components/react-components";
120
+ import {
121
+ ReactConfigProvider,
122
+ ReactSelect,
123
+ } from "@canvas-components/react-components";
116
124
 
117
125
  <ReactConfigProvider
118
126
  theme={{
@@ -127,7 +135,7 @@ import { ReactConfigProvider, ReactSelect } from "@canvas-components/react-compo
127
135
  }}
128
136
  >
129
137
  <ReactSelect options={[{ label: "选项一", value: "one" }]} />
130
- </ReactConfigProvider>
138
+ </ReactConfigProvider>;
131
139
  ```
132
140
 
133
141
  ## 组件列表
@@ -187,6 +195,29 @@ import { ReactConfigProvider, ReactSelect } from "@canvas-components/react-compo
187
195
 
188
196
  ## 属性说明
189
197
 
198
+ ### `ReactPagination`
199
+
200
+ ```tsx
201
+ <ReactPagination
202
+ current={current}
203
+ total={1000}
204
+ pageSize={pageSize}
205
+ showSizeChanger
206
+ allowPageSizeInput
207
+ minPageSize={5}
208
+ maxPageSize={500}
209
+ onChange={setCurrent}
210
+ onPageSizeChange={(page, size) => {
211
+ setCurrent(page);
212
+ setPageSize(size);
213
+ }}
214
+ />
215
+ ```
216
+
217
+ `allowPageSizeInput` 默认关闭。开启后可在每页条数下拉框中输入正整数,按回车或失焦提交;合法值触发 `onPageSizeChange(1, size)`,并在当前组件挂载期间作为去重、升序的候选项保留,切换到其他条数后仍可再次选择。空候选列表也支持输入。自定义选项右侧可点击删除,预设选项和默认每页 10 条不可删除;删除当前选中的自定义项时回到每页 10 条并切回第 1 页,若 10 超出配置范围则取范围内最近的值。删除其他项不改变当前分页,删除后仍可重新输入该值。
218
+
219
+ `minPageSize` 默认 `1`,`maxPageSize` 默认 `Number.MAX_SAFE_INTEGER`,上下限均包含边界。非法或越界输入不触发变更,失焦恢复当前值;范围外的候选项不可选择。无效的上下限使用默认值,最大值小于最小值时按最小值处理。组件不会主动修改外部传入的当前 `pageSize`。
220
+
190
221
  ### `ReactTag`
191
222
 
192
223
  ```tsx
@@ -215,89 +246,89 @@ import { ReactConfigProvider, ReactSelect } from "@canvas-components/react-compo
215
246
 
216
247
  ### `ReactPopupProps`
217
248
 
218
- | 属性 | 类型 | 说明 |
219
- | --- | --- | --- |
220
- | `open` | `boolean` | 是否显示 |
221
- | `anchorRef` | `RefObject<HTMLElement \| null>` | 锚点元素引用 |
222
- | `anchorRect` | `PopupAnchorRect \| null` | 外部传入的锚点矩形 |
223
- | `placement` | `"bottomLeft" \| "bottomRight" \| "topLeft" \| "topRight" \| "rightTop" \| "rightBottom" \| "leftTop" \| "leftBottom"` | 弹层位置 |
224
- | `offset` | `number` | 与锚点间距 |
225
- | `matchAnchorWidth` | `boolean` | 是否跟随锚点宽度 |
226
- | `minWidth` | `number` | 最小宽度 |
227
- | `zIndex` | `number` | 层级 |
228
- | `className` | `string` | 外层类名 |
229
- | `surfaceClassName` | `string` | 面板类名 |
230
- | `style` | `CSSProperties` | 外层样式 |
231
- | `surfaceStyle` | `CSSProperties` | 面板样式 |
232
- | `onOpenChange` | `(open: boolean) => void` | 打开状态变化回调 |
233
- | `children` | `ReactNode` | 弹层内容 |
249
+ | 属性 | 类型 | 说明 |
250
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | ------------------ |
251
+ | `open` | `boolean` | 是否显示 |
252
+ | `anchorRef` | `RefObject<HTMLElement \| null>` | 锚点元素引用 |
253
+ | `anchorRect` | `PopupAnchorRect \| null` | 外部传入的锚点矩形 |
254
+ | `placement` | `"bottomLeft" \| "bottomRight" \| "topLeft" \| "topRight" \| "rightTop" \| "rightBottom" \| "leftTop" \| "leftBottom"` | 弹层位置 |
255
+ | `offset` | `number` | 与锚点间距 |
256
+ | `matchAnchorWidth` | `boolean` | 是否跟随锚点宽度 |
257
+ | `minWidth` | `number` | 最小宽度 |
258
+ | `zIndex` | `number` | 层级 |
259
+ | `className` | `string` | 外层类名 |
260
+ | `surfaceClassName` | `string` | 面板类名 |
261
+ | `style` | `CSSProperties` | 外层样式 |
262
+ | `surfaceStyle` | `CSSProperties` | 面板样式 |
263
+ | `onOpenChange` | `(open: boolean) => void` | 打开状态变化回调 |
264
+ | `children` | `ReactNode` | 弹层内容 |
234
265
 
235
266
  ### `ReactPopconfirmProps`
236
267
 
237
- | 属性 | 类型 | 说明 |
238
- | --- | --- | --- |
239
- | `children` | `ReactNode` | 触发器内容 |
240
- | `title` | `ReactNode` | 标题 |
241
- | `description` | `ReactNode` | 描述文本 |
242
- | `open` / `defaultOpen` | `boolean` | 受控 / 非受控打开状态 |
243
- | `disabled` | `boolean` | 是否禁用 |
244
- | `placement` | `ReactPopconfirmPlacement` | 弹层位置 |
245
- | `offset` | `number` | 触发器与弹层间距 |
246
- | `zIndex` | `number` | 层级 |
247
- | `icon` | `ReactNode` | 自定义图标,传 `null` 隐藏默认图标 |
248
- | `okText` / `cancelText` | `ReactNode` | 按钮文案 |
249
- | `showCancel` | `boolean` | 是否显示取消按钮 |
250
- | `confirmLoading` | `boolean` | 是否强制确认按钮 loading |
251
- | `okButtonProps` / `cancelButtonProps` | `ReactPopconfirmButtonProps` | 按钮透传属性 |
252
- | `onConfirm` | `(event) => void \| Promise<void>` | 点击确认回调,支持异步 |
253
- | `onCancel` | `(event) => void` | 点击取消回调 |
254
- | `onOpenChange` | `(open: boolean) => void` | 状态变化回调 |
255
- | `className` / `style` | `string` / `CSSProperties` | 触发器样式 |
256
- | `popupClassName` / `popupStyle` | `string` / `CSSProperties` | 弹层面板样式 |
268
+ | 属性 | 类型 | 说明 |
269
+ | ------------------------------------- | ---------------------------------- | ---------------------------------- |
270
+ | `children` | `ReactNode` | 触发器内容 |
271
+ | `title` | `ReactNode` | 标题 |
272
+ | `description` | `ReactNode` | 描述文本 |
273
+ | `open` / `defaultOpen` | `boolean` | 受控 / 非受控打开状态 |
274
+ | `disabled` | `boolean` | 是否禁用 |
275
+ | `placement` | `ReactPopconfirmPlacement` | 弹层位置 |
276
+ | `offset` | `number` | 触发器与弹层间距 |
277
+ | `zIndex` | `number` | 层级 |
278
+ | `icon` | `ReactNode` | 自定义图标,传 `null` 隐藏默认图标 |
279
+ | `okText` / `cancelText` | `ReactNode` | 按钮文案 |
280
+ | `showCancel` | `boolean` | 是否显示取消按钮 |
281
+ | `confirmLoading` | `boolean` | 是否强制确认按钮 loading |
282
+ | `okButtonProps` / `cancelButtonProps` | `ReactPopconfirmButtonProps` | 按钮透传属性 |
283
+ | `onConfirm` | `(event) => void \| Promise<void>` | 点击确认回调,支持异步 |
284
+ | `onCancel` | `(event) => void` | 点击取消回调 |
285
+ | `onOpenChange` | `(open: boolean) => void` | 状态变化回调 |
286
+ | `className` / `style` | `string` / `CSSProperties` | 触发器样式 |
287
+ | `popupClassName` / `popupStyle` | `string` / `CSSProperties` | 弹层面板样式 |
257
288
 
258
289
  ### `ReactTransitionProps`
259
290
 
260
- | 属性 | 类型 | 说明 |
261
- | --- | --- | --- |
262
- | `show` | `boolean` | 是否显示内容 |
263
- | `name` | `string` | CSS 类名前缀 |
264
- | `duration` | `number \| { enter?: number; leave?: number }` | 动画时长 |
265
- | `appear` | `boolean` | 首次显示是否执行进入动画 |
266
- | `children` | `(state: ReactTransitionState) => ReactNode` | 根据阶段渲染内容 |
291
+ | 属性 | 类型 | 说明 |
292
+ | ---------- | ---------------------------------------------- | ------------------------ |
293
+ | `show` | `boolean` | 是否显示内容 |
294
+ | `name` | `string` | CSS 类名前缀 |
295
+ | `duration` | `number \| { enter?: number; leave?: number }` | 动画时长 |
296
+ | `appear` | `boolean` | 首次显示是否执行进入动画 |
297
+ | `children` | `(state: ReactTransitionState) => ReactNode` | 根据阶段渲染内容 |
267
298
 
268
299
  ### `ReactVirtualListProps<Item>`
269
300
 
270
- | 属性 | 类型 | 说明 |
271
- | --- | --- | --- |
272
- | `items` | `Item[]` | 数据列表 |
273
- | `height` | `number` | 视口高度 |
274
- | `itemHeight` | `number` | 固定项高度 |
275
- | `overscan` | `number` | 预渲染项数 |
276
- | `renderItem` | `(item, index, meta) => ReactNode` | 渲染函数 |
277
- | `itemKey` | `(item, index) => string \| number` | 列表 key |
301
+ | 属性 | 类型 | 说明 |
302
+ | ------------ | ----------------------------------- | ---------- |
303
+ | `items` | `Item[]` | 数据列表 |
304
+ | `height` | `number` | 视口高度 |
305
+ | `itemHeight` | `number` | 固定项高度 |
306
+ | `overscan` | `number` | 预渲染项数 |
307
+ | `renderItem` | `(item, index, meta) => ReactNode` | 渲染函数 |
308
+ | `itemKey` | `(item, index) => string \| number` | 列表 key |
278
309
 
279
310
  ## 方法说明
280
311
 
281
312
  ### `ReactTransition`
282
313
 
283
- | 导出 | 说明 |
284
- | --- | --- |
285
- | `ReactTransition` | 无包装过渡组件,离场动画结束后自动卸载 |
286
- | `useReactTransition` | 获取 Vue 风格过渡阶段和 className |
314
+ | 导出 | 说明 |
315
+ | -------------------- | -------------------------------------- |
316
+ | `ReactTransition` | 无包装过渡组件,离场动画结束后自动卸载 |
317
+ | `useReactTransition` | 获取 Vue 风格过渡阶段和 className |
287
318
 
288
319
  ### `ReactVirtualListRef`
289
320
 
290
- | 方法 | 说明 |
291
- | --- | --- |
292
- | `scrollTo` | 滚动到指定位置 |
321
+ | 方法 | 说明 |
322
+ | --------------- | -------------- |
323
+ | `scrollTo` | 滚动到指定位置 |
293
324
  | `scrollToIndex` | 滚动到指定索引 |
294
325
 
295
326
  ## 其他导出
296
327
 
297
- | 导出 | 说明 |
298
- | --- | --- |
328
+ | 导出 | 说明 |
329
+ | ---------------------------------------------------- | -------------- |
299
330
  | `ReactContentScaleProvider` / `useReactContentScale` | 内容缩放上下文 |
300
- | `REACT_COMPONENT_PREFIX` | 统一类名前缀 |
331
+ | `REACT_COMPONENT_PREFIX` | 统一类名前缀 |
301
332
 
302
333
  ## 说明
303
334
 
package/dist/index.cjs CHANGED
@@ -3578,6 +3578,7 @@ var ReactSelect = React.forwardRef(
3578
3578
  onFocus,
3579
3579
  onKeyDown,
3580
3580
  options,
3581
+ renderOptionExtra,
3581
3582
  optionHeight = 32,
3582
3583
  listHeight = 224,
3583
3584
  placeholder,
@@ -3773,7 +3774,10 @@ var ReactSelect = React.forwardRef(
3773
3774
  openSelect();
3774
3775
  return;
3775
3776
  }
3776
- const targetOption = resolveSelectEnterOption(filteredOptions, activeIndex);
3777
+ const targetOption = resolveSelectEnterOption(
3778
+ filteredOptions,
3779
+ activeIndex
3780
+ );
3777
3781
  if (targetOption && !targetOption.disabled) {
3778
3782
  selectOption(targetOption, "keyboard");
3779
3783
  } else if (allowCustomValue && keyword.trim()) {
@@ -3863,7 +3867,7 @@ var ReactSelect = React.forwardRef(
3863
3867
  const isSelected = String(option.value) === String(value ?? "");
3864
3868
  const isActive = optionIndex === activeIndex;
3865
3869
  const isSearching = showSearch && searchKeyword.trim().length > 0;
3866
- return /* @__PURE__ */ jsxRuntime.jsx(
3870
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3867
3871
  "div",
3868
3872
  {
3869
3873
  className: joinClassNames(
@@ -3886,17 +3890,20 @@ var ReactSelect = React.forwardRef(
3886
3890
  }
3887
3891
  selectOption(option, "mouse");
3888
3892
  },
3889
- children: /* @__PURE__ */ jsxRuntime.jsx(
3890
- "span",
3891
- {
3892
- className: `${REACT_COMPONENT_PREFIX}-select-option-label`,
3893
- title: option.label,
3894
- children: renderSelectOptionLabel(
3895
- option.label,
3896
- showSearch ? searchKeyword : ""
3897
- )
3898
- }
3899
- )
3893
+ children: [
3894
+ /* @__PURE__ */ jsxRuntime.jsx(
3895
+ "span",
3896
+ {
3897
+ className: `${REACT_COMPONENT_PREFIX}-select-option-label`,
3898
+ title: option.label,
3899
+ children: renderSelectOptionLabel(
3900
+ option.label,
3901
+ showSearch ? searchKeyword : ""
3902
+ )
3903
+ }
3904
+ ),
3905
+ renderOptionExtra?.(option)
3906
+ ]
3900
3907
  },
3901
3908
  String(option.value)
3902
3909
  );
@@ -3937,7 +3944,8 @@ function scoreSelectMatch(text, search) {
3937
3944
  return (index === 0 ? 2 : 1) + search.length / normalized.length;
3938
3945
  }
3939
3946
  function resolveSelectEnterOption(options, activeIndex = -1) {
3940
- if (options[activeIndex] && !options[activeIndex].disabled) return options[activeIndex];
3947
+ if (options[activeIndex] && !options[activeIndex].disabled)
3948
+ return options[activeIndex];
3941
3949
  return options.find((option) => !option.disabled);
3942
3950
  }
3943
3951
  function resolveSelectKeywordOnOpen(selectedLabel) {
@@ -9520,15 +9528,19 @@ function ReactScrollbarPositionDialog(props) {
9520
9528
  }
9521
9529
 
9522
9530
  // src/components/pagination/style.css
9523
- styleInject('@import "../../styles/variables.css";\n.canvas-react-pagination {\n --canvas-react-pagination-control-size: 32px;\n --canvas-react-pagination-gap: 8px;\n --canvas-react-pagination-padding-inline: 10px;\n --canvas-react-control-height: var(--canvas-react-pagination-control-size);\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: var(--canvas-react-pagination-gap);\n color: var(--canvas-react-color-text);\n font-size: var(--canvas-react-font-size);\n white-space: nowrap;\n}\n.canvas-react-pagination-small {\n --canvas-react-pagination-control-size: 24px;\n --canvas-react-pagination-gap: 4px;\n --canvas-react-pagination-padding-inline: 6px;\n}\n.canvas-react-pagination > .canvas-react-select-wrap {\n flex: 0 0 auto;\n width: auto;\n height: auto;\n}\n.canvas-react-pagination > .canvas-react-select-wrap > .canvas-react-select {\n flex-shrink: 0;\n}\n.canvas-react-pagination-large {\n --canvas-react-pagination-control-size: 40px;\n --canvas-react-pagination-gap: 10px;\n --canvas-react-pagination-padding-inline: 14px;\n}\n.canvas-react-pagination-pages {\n display: inline-flex;\n align-items: center;\n gap: var(--canvas-react-pagination-gap);\n flex-wrap: nowrap;\n}\n.canvas-react-pagination-button,\n.canvas-react-pagination-page {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--canvas-react-pagination-control-size);\n height: var(--canvas-react-pagination-control-size);\n padding: 0 var(--canvas-react-pagination-padding-inline);\n border: 1px solid var(--canvas-react-color-border);\n border-radius: var(--canvas-react-radius-sm);\n background: var(--canvas-react-color-bg);\n color: var(--canvas-react-color-text);\n cursor: pointer;\n transition:\n border-color 0.2s ease,\n color 0.2s ease,\n background-color 0.2s ease;\n}\n.canvas-react-pagination-button {\n position: relative;\n width: var(--canvas-react-pagination-control-size);\n min-width: var(--canvas-react-pagination-control-size);\n padding: 0;\n}\n.canvas-react-pagination-button:hover,\n.canvas-react-pagination-page:hover {\n border-color: var(--canvas-react-color-primary-hover);\n color: var(--canvas-react-color-primary-hover);\n}\n.canvas-react-pagination-page-active {\n border-color: var(--canvas-react-color-primary);\n background: var(--canvas-react-color-bg-active);\n color: var(--canvas-react-color-primary);\n}\n.canvas-react-pagination-button:disabled,\n.canvas-react-pagination-page:disabled {\n opacity: 0.48;\n cursor: not-allowed;\n}\n.canvas-react-pagination-ellipsis {\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-pagination-arrow {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 7px;\n height: 7px;\n border-right: 1.5px solid var(--canvas-react-color-text-muted);\n border-bottom: 1.5px solid var(--canvas-react-color-text-muted);\n transition:\n border-color 0.18s ease,\n transform 0.18s ease,\n opacity 0.18s ease;\n}\n.canvas-react-pagination-arrow-prev {\n transform: translate(-42%, -50%) rotate(135deg);\n}\n.canvas-react-pagination-arrow-next {\n transform: translate(-58%, -50%) rotate(-45deg);\n}\n.canvas-react-pagination-button:hover .canvas-react-pagination-arrow {\n border-color: var(--canvas-react-color-primary-hover);\n}\n.canvas-react-pagination-button:disabled .canvas-react-pagination-arrow {\n opacity: 0.56;\n}\n.canvas-react-pagination-quick {\n display: inline-flex;\n align-items: center;\n gap: var(--canvas-react-pagination-gap);\n flex-wrap: nowrap;\n}\n.canvas-react-pagination-total {\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-pagination button,\n.canvas-react-pagination input {\n font-family: inherit;\n font-weight: inherit;\n font-size: inherit;\n}\n');
9531
+ styleInject('@import "../../styles/variables.css";\n.canvas-react-pagination {\n --canvas-react-pagination-control-size: 32px;\n --canvas-react-pagination-gap: 8px;\n --canvas-react-pagination-padding-inline: 10px;\n --canvas-react-control-height: var(--canvas-react-pagination-control-size);\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: var(--canvas-react-pagination-gap);\n color: var(--canvas-react-color-text);\n font-size: var(--canvas-react-font-size);\n white-space: nowrap;\n}\n.canvas-react-pagination-small {\n --canvas-react-pagination-control-size: 24px;\n --canvas-react-pagination-gap: 4px;\n --canvas-react-pagination-padding-inline: 6px;\n}\n.canvas-react-pagination > .canvas-react-select-wrap {\n flex: 0 0 auto;\n width: auto;\n height: auto;\n}\n.canvas-react-pagination > .canvas-react-select-wrap > .canvas-react-select {\n flex-shrink: 0;\n}\n.canvas-react-pagination-large {\n --canvas-react-pagination-control-size: 40px;\n --canvas-react-pagination-gap: 10px;\n --canvas-react-pagination-padding-inline: 14px;\n}\n.canvas-react-pagination-pages {\n display: inline-flex;\n align-items: center;\n gap: var(--canvas-react-pagination-gap);\n flex-wrap: nowrap;\n}\n.canvas-react-pagination-button,\n.canvas-react-pagination-page {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--canvas-react-pagination-control-size);\n height: var(--canvas-react-pagination-control-size);\n padding: 0 var(--canvas-react-pagination-padding-inline);\n border: 1px solid var(--canvas-react-color-border);\n border-radius: var(--canvas-react-radius-sm);\n background: var(--canvas-react-color-bg);\n color: var(--canvas-react-color-text);\n cursor: pointer;\n transition:\n border-color 0.2s ease,\n color 0.2s ease,\n background-color 0.2s ease;\n}\n.canvas-react-pagination-button {\n position: relative;\n width: var(--canvas-react-pagination-control-size);\n min-width: var(--canvas-react-pagination-control-size);\n padding: 0;\n}\n.canvas-react-pagination-button:hover,\n.canvas-react-pagination-page:hover {\n border-color: var(--canvas-react-color-primary-hover);\n color: var(--canvas-react-color-primary-hover);\n}\n.canvas-react-pagination-page-active {\n border-color: var(--canvas-react-color-primary);\n background: var(--canvas-react-color-bg-active);\n color: var(--canvas-react-color-primary);\n}\n.canvas-react-pagination-button:disabled,\n.canvas-react-pagination-page:disabled {\n opacity: 0.48;\n cursor: not-allowed;\n}\n.canvas-react-pagination-ellipsis {\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-pagination-arrow {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 7px;\n height: 7px;\n border-right: 1.5px solid var(--canvas-react-color-text-muted);\n border-bottom: 1.5px solid var(--canvas-react-color-text-muted);\n transition:\n border-color 0.18s ease,\n transform 0.18s ease,\n opacity 0.18s ease;\n}\n.canvas-react-pagination-arrow-prev {\n transform: translate(-42%, -50%) rotate(135deg);\n}\n.canvas-react-pagination-arrow-next {\n transform: translate(-58%, -50%) rotate(-45deg);\n}\n.canvas-react-pagination-button:hover .canvas-react-pagination-arrow {\n border-color: var(--canvas-react-color-primary-hover);\n}\n.canvas-react-pagination-button:disabled .canvas-react-pagination-arrow {\n opacity: 0.56;\n}\n.canvas-react-pagination-quick {\n display: inline-flex;\n align-items: center;\n gap: var(--canvas-react-pagination-gap);\n flex-wrap: nowrap;\n}\n.canvas-react-pagination-total {\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-pagination button,\n.canvas-react-pagination input {\n font-family: inherit;\n font-weight: inherit;\n font-size: inherit;\n}\n.canvas-react-pagination-remove-size {\n flex: 0 0 auto;\n margin-left: 6px;\n padding: 0 4px;\n border: 0;\n border-radius: var(--canvas-react-radius-sm);\n background: transparent;\n color: var(--canvas-react-color-text-muted);\n font: inherit;\n cursor: pointer;\n}\n.canvas-react-pagination-remove-size:hover,\n.canvas-react-pagination-remove-size:focus-visible {\n background: var(--canvas-react-color-bg-hover);\n color: var(--canvas-react-color-primary);\n}\n.canvas-react-pagination-remove-size:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n');
9532
+ var DEFAULT_PAGE_SIZE = 10;
9524
9533
  var DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
9525
9534
  function ReactPagination(props) {
9526
9535
  const {
9536
+ allowPageSizeInput = false,
9537
+ minPageSize = 1,
9538
+ maxPageSize = Number.MAX_SAFE_INTEGER,
9527
9539
  current,
9528
9540
  disabled,
9529
9541
  onChange,
9530
9542
  onPageSizeChange,
9531
- pageSize = 10,
9543
+ pageSize = DEFAULT_PAGE_SIZE,
9532
9544
  pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
9533
9545
  showLessItems,
9534
9546
  showQuickJumper,
@@ -9542,12 +9554,56 @@ function ReactPagination(props) {
9542
9554
  } = props;
9543
9555
  const style = { ...rootStyle, ...styles?.root };
9544
9556
  const [quickValue, setQuickValue] = React.useState("");
9557
+ const [customPageSizes, setCustomPageSizes] = React.useState([]);
9558
+ const pageSizeDraft = React.useRef(null);
9559
+ const sizeChangerRef = React.useRef(null);
9560
+ const minimum = Number.isSafeInteger(minPageSize) && minPageSize > 0 ? minPageSize : 1;
9561
+ const maximum = Number.isSafeInteger(maxPageSize) && maxPageSize > 0 ? Math.max(minimum, maxPageSize) : Number.MAX_SAFE_INTEGER;
9562
+ const defaultPageSize = Math.min(
9563
+ maximum,
9564
+ Math.max(minimum, DEFAULT_PAGE_SIZE)
9565
+ );
9566
+ const isValidPageSize = (value) => Number.isSafeInteger(value) && value >= minimum && value <= maximum;
9545
9567
  const resolvedPageSizeOptions = React.useMemo(() => {
9546
- if (pageSizeOptions.length === 0 || pageSizeOptions.includes(pageSize) || !Number.isSafeInteger(pageSize) || pageSize <= 0) {
9547
- return pageSizeOptions;
9568
+ const options = allowPageSizeInput ? [.../* @__PURE__ */ new Set([...pageSizeOptions, ...customPageSizes])].sort(
9569
+ (a, b) => a - b
9570
+ ) : pageSizeOptions;
9571
+ if (options.length === 0 && !allowPageSizeInput || options.includes(pageSize) || !Number.isSafeInteger(pageSize) || pageSize <= 0) {
9572
+ return options;
9573
+ }
9574
+ return [...options, pageSize].sort((left, right) => left - right);
9575
+ }, [pageSizeOptions, pageSize, customPageSizes, allowPageSizeInput]);
9576
+ const changePageSize = (value) => {
9577
+ pageSizeDraft.current = null;
9578
+ if (disabled || !isValidPageSize(value)) return;
9579
+ if (allowPageSizeInput) {
9580
+ setCustomPageSizes(
9581
+ (sizes) => sizes.includes(value) ? sizes : [...sizes, value]
9582
+ );
9548
9583
  }
9549
- return [...pageSizeOptions, pageSize].sort((left, right) => left - right);
9550
- }, [pageSizeOptions, pageSize]);
9584
+ onPageSizeChange?.(1, value);
9585
+ };
9586
+ const removeCustomPageSize = (value) => {
9587
+ if (disabled) return;
9588
+ pageSizeDraft.current = null;
9589
+ sizeChangerRef.current?.blur();
9590
+ setCustomPageSizes((sizes) => sizes.filter((size2) => size2 !== value));
9591
+ if (value === pageSize) {
9592
+ onPageSizeChange?.(1, defaultPageSize);
9593
+ }
9594
+ };
9595
+ const commitPageSizeInput = () => {
9596
+ const draft = pageSizeDraft.current;
9597
+ pageSizeDraft.current = null;
9598
+ if (!allowPageSizeInput || draft == null || !/^\d+$/.test(draft.trim()))
9599
+ return;
9600
+ changePageSize(Number(draft.trim()));
9601
+ };
9602
+ const pageSizeSelectOptions = resolvedPageSizeOptions.map((size2) => ({
9603
+ label: `${size2} / \u9875`,
9604
+ value: size2,
9605
+ ...!isValidPageSize(size2) ? { disabled: true } : {}
9606
+ }));
9551
9607
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
9552
9608
  const safeCurrent = clampPage(current, totalPages);
9553
9609
  const range = total === 0 ? [0, 0] : [
@@ -9679,26 +9735,65 @@ function ReactPagination(props) {
9679
9735
  )
9680
9736
  }
9681
9737
  ),
9682
- showSizeChanger && resolvedPageSizeOptions.length ? /* @__PURE__ */ jsxRuntime.jsx(
9738
+ showSizeChanger && (allowPageSizeInput || resolvedPageSizeOptions.length) ? /* @__PURE__ */ jsxRuntime.jsx(
9683
9739
  ReactSelect,
9684
9740
  {
9741
+ ref: sizeChangerRef,
9685
9742
  value: String(pageSize),
9686
9743
  disabled,
9744
+ "aria-label": "\u6BCF\u9875\u6761\u6570",
9745
+ allowClear: false,
9746
+ showSearch: allowPageSizeInput,
9747
+ allowCustomValue: allowPageSizeInput,
9748
+ inputMode: allowPageSizeInput ? "numeric" : void 0,
9749
+ onInputValueChange: (value) => {
9750
+ pageSizeDraft.current = value;
9751
+ },
9752
+ onKeyDown: (event) => {
9753
+ if (event.key === "Escape") pageSizeDraft.current = null;
9754
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && pageSizeDraft.current != null && filterSelectOptions(
9755
+ pageSizeSelectOptions,
9756
+ pageSizeDraft.current,
9757
+ true
9758
+ ).some((option) => !option.disabled)) {
9759
+ pageSizeDraft.current = null;
9760
+ }
9761
+ if (allowPageSizeInput && event.key === "Enter" && pageSizeDraft.current != null) {
9762
+ event.preventDefault();
9763
+ commitPageSizeInput();
9764
+ event.currentTarget.blur();
9765
+ }
9766
+ },
9767
+ onBlur: () => commitPageSizeInput(),
9687
9768
  style: {
9688
9769
  width: resolvePaginationSelectWidth(size, resolvedPageSizeOptions),
9689
9770
  ...styles?.sizeChanger
9690
9771
  },
9691
- options: resolvedPageSizeOptions.map((size2) => ({
9692
- label: `${size2} / \u9875`,
9693
- value: size2
9694
- })),
9695
- onChange: (value) => {
9696
- const nextPageSize = Number(value);
9697
- if (!Number.isFinite(nextPageSize)) {
9698
- return;
9699
- }
9700
- onPageSizeChange?.(1, nextPageSize);
9701
- }
9772
+ options: pageSizeSelectOptions,
9773
+ renderOptionExtra: (option) => {
9774
+ const value = Number(option.value);
9775
+ if (!allowPageSizeInput || pageSizeOptions.includes(value) || value === defaultPageSize)
9776
+ return null;
9777
+ return /* @__PURE__ */ jsxRuntime.jsx(
9778
+ "button",
9779
+ {
9780
+ type: "button",
9781
+ className: `${REACT_COMPONENT_PREFIX}-pagination-remove-size`,
9782
+ "aria-label": `\u5220\u9664\u6BCF\u9875 ${value} \u6761`,
9783
+ disabled,
9784
+ onMouseDown: (event) => {
9785
+ event.preventDefault();
9786
+ event.stopPropagation();
9787
+ },
9788
+ onClick: (event) => {
9789
+ event.stopPropagation();
9790
+ removeCustomPageSize(value);
9791
+ },
9792
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
9793
+ }
9794
+ );
9795
+ },
9796
+ onChange: (value) => changePageSize(Number(value))
9702
9797
  }
9703
9798
  ) : null,
9704
9799
  showQuickJumper ? /* @__PURE__ */ jsxRuntime.jsxs(
package/dist/index.d.cts CHANGED
@@ -3353,6 +3353,12 @@ interface ReactPaginationProps {
3353
3353
  * @default [10, 20, 50, 100]
3354
3354
  */
3355
3355
  pageSizeOptions?: number[];
3356
+ /** 是否允许手动输入每页条数,回车或失焦后生效。默认 false。 */
3357
+ allowPageSizeInput?: boolean;
3358
+ /** 可选或输入的最小每页条数,默认 1。 */
3359
+ minPageSize?: number;
3360
+ /** 可选或输入的最大每页条数,默认不限制(安全整数范围内)。 */
3361
+ maxPageSize?: number;
3356
3362
  /**
3357
3363
  * 是否禁用。
3358
3364
  */
@@ -3920,6 +3926,8 @@ interface ReactSelectProps extends Omit<ReactInputProps, "value" | "onChange" |
3920
3926
  * 选项列表。
3921
3927
  */
3922
3928
  options: ReactSelectOption[];
3929
+ /** 选项右侧的扩展内容,可用于额外操作。 */
3930
+ renderOptionExtra?: (option: ReactSelectOption) => ReactNode;
3923
3931
  /**
3924
3932
  * 值变化回调。
3925
3933
  */
package/dist/index.d.ts CHANGED
@@ -3353,6 +3353,12 @@ interface ReactPaginationProps {
3353
3353
  * @default [10, 20, 50, 100]
3354
3354
  */
3355
3355
  pageSizeOptions?: number[];
3356
+ /** 是否允许手动输入每页条数,回车或失焦后生效。默认 false。 */
3357
+ allowPageSizeInput?: boolean;
3358
+ /** 可选或输入的最小每页条数,默认 1。 */
3359
+ minPageSize?: number;
3360
+ /** 可选或输入的最大每页条数,默认不限制(安全整数范围内)。 */
3361
+ maxPageSize?: number;
3356
3362
  /**
3357
3363
  * 是否禁用。
3358
3364
  */
@@ -3920,6 +3926,8 @@ interface ReactSelectProps extends Omit<ReactInputProps, "value" | "onChange" |
3920
3926
  * 选项列表。
3921
3927
  */
3922
3928
  options: ReactSelectOption[];
3929
+ /** 选项右侧的扩展内容,可用于额外操作。 */
3930
+ renderOptionExtra?: (option: ReactSelectOption) => ReactNode;
3923
3931
  /**
3924
3932
  * 值变化回调。
3925
3933
  */
package/dist/index.js CHANGED
@@ -3557,6 +3557,7 @@ var ReactSelect = forwardRef(
3557
3557
  onFocus,
3558
3558
  onKeyDown,
3559
3559
  options,
3560
+ renderOptionExtra,
3560
3561
  optionHeight = 32,
3561
3562
  listHeight = 224,
3562
3563
  placeholder,
@@ -3752,7 +3753,10 @@ var ReactSelect = forwardRef(
3752
3753
  openSelect();
3753
3754
  return;
3754
3755
  }
3755
- const targetOption = resolveSelectEnterOption(filteredOptions, activeIndex);
3756
+ const targetOption = resolveSelectEnterOption(
3757
+ filteredOptions,
3758
+ activeIndex
3759
+ );
3756
3760
  if (targetOption && !targetOption.disabled) {
3757
3761
  selectOption(targetOption, "keyboard");
3758
3762
  } else if (allowCustomValue && keyword.trim()) {
@@ -3842,7 +3846,7 @@ var ReactSelect = forwardRef(
3842
3846
  const isSelected = String(option.value) === String(value ?? "");
3843
3847
  const isActive = optionIndex === activeIndex;
3844
3848
  const isSearching = showSearch && searchKeyword.trim().length > 0;
3845
- return /* @__PURE__ */ jsx(
3849
+ return /* @__PURE__ */ jsxs(
3846
3850
  "div",
3847
3851
  {
3848
3852
  className: joinClassNames(
@@ -3865,17 +3869,20 @@ var ReactSelect = forwardRef(
3865
3869
  }
3866
3870
  selectOption(option, "mouse");
3867
3871
  },
3868
- children: /* @__PURE__ */ jsx(
3869
- "span",
3870
- {
3871
- className: `${REACT_COMPONENT_PREFIX}-select-option-label`,
3872
- title: option.label,
3873
- children: renderSelectOptionLabel(
3874
- option.label,
3875
- showSearch ? searchKeyword : ""
3876
- )
3877
- }
3878
- )
3872
+ children: [
3873
+ /* @__PURE__ */ jsx(
3874
+ "span",
3875
+ {
3876
+ className: `${REACT_COMPONENT_PREFIX}-select-option-label`,
3877
+ title: option.label,
3878
+ children: renderSelectOptionLabel(
3879
+ option.label,
3880
+ showSearch ? searchKeyword : ""
3881
+ )
3882
+ }
3883
+ ),
3884
+ renderOptionExtra?.(option)
3885
+ ]
3879
3886
  },
3880
3887
  String(option.value)
3881
3888
  );
@@ -3916,7 +3923,8 @@ function scoreSelectMatch(text, search) {
3916
3923
  return (index === 0 ? 2 : 1) + search.length / normalized.length;
3917
3924
  }
3918
3925
  function resolveSelectEnterOption(options, activeIndex = -1) {
3919
- if (options[activeIndex] && !options[activeIndex].disabled) return options[activeIndex];
3926
+ if (options[activeIndex] && !options[activeIndex].disabled)
3927
+ return options[activeIndex];
3920
3928
  return options.find((option) => !option.disabled);
3921
3929
  }
3922
3930
  function resolveSelectKeywordOnOpen(selectedLabel) {
@@ -9499,15 +9507,19 @@ function ReactScrollbarPositionDialog(props) {
9499
9507
  }
9500
9508
 
9501
9509
  // src/components/pagination/style.css
9502
- styleInject('@import "../../styles/variables.css";\n.canvas-react-pagination {\n --canvas-react-pagination-control-size: 32px;\n --canvas-react-pagination-gap: 8px;\n --canvas-react-pagination-padding-inline: 10px;\n --canvas-react-control-height: var(--canvas-react-pagination-control-size);\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: var(--canvas-react-pagination-gap);\n color: var(--canvas-react-color-text);\n font-size: var(--canvas-react-font-size);\n white-space: nowrap;\n}\n.canvas-react-pagination-small {\n --canvas-react-pagination-control-size: 24px;\n --canvas-react-pagination-gap: 4px;\n --canvas-react-pagination-padding-inline: 6px;\n}\n.canvas-react-pagination > .canvas-react-select-wrap {\n flex: 0 0 auto;\n width: auto;\n height: auto;\n}\n.canvas-react-pagination > .canvas-react-select-wrap > .canvas-react-select {\n flex-shrink: 0;\n}\n.canvas-react-pagination-large {\n --canvas-react-pagination-control-size: 40px;\n --canvas-react-pagination-gap: 10px;\n --canvas-react-pagination-padding-inline: 14px;\n}\n.canvas-react-pagination-pages {\n display: inline-flex;\n align-items: center;\n gap: var(--canvas-react-pagination-gap);\n flex-wrap: nowrap;\n}\n.canvas-react-pagination-button,\n.canvas-react-pagination-page {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--canvas-react-pagination-control-size);\n height: var(--canvas-react-pagination-control-size);\n padding: 0 var(--canvas-react-pagination-padding-inline);\n border: 1px solid var(--canvas-react-color-border);\n border-radius: var(--canvas-react-radius-sm);\n background: var(--canvas-react-color-bg);\n color: var(--canvas-react-color-text);\n cursor: pointer;\n transition:\n border-color 0.2s ease,\n color 0.2s ease,\n background-color 0.2s ease;\n}\n.canvas-react-pagination-button {\n position: relative;\n width: var(--canvas-react-pagination-control-size);\n min-width: var(--canvas-react-pagination-control-size);\n padding: 0;\n}\n.canvas-react-pagination-button:hover,\n.canvas-react-pagination-page:hover {\n border-color: var(--canvas-react-color-primary-hover);\n color: var(--canvas-react-color-primary-hover);\n}\n.canvas-react-pagination-page-active {\n border-color: var(--canvas-react-color-primary);\n background: var(--canvas-react-color-bg-active);\n color: var(--canvas-react-color-primary);\n}\n.canvas-react-pagination-button:disabled,\n.canvas-react-pagination-page:disabled {\n opacity: 0.48;\n cursor: not-allowed;\n}\n.canvas-react-pagination-ellipsis {\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-pagination-arrow {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 7px;\n height: 7px;\n border-right: 1.5px solid var(--canvas-react-color-text-muted);\n border-bottom: 1.5px solid var(--canvas-react-color-text-muted);\n transition:\n border-color 0.18s ease,\n transform 0.18s ease,\n opacity 0.18s ease;\n}\n.canvas-react-pagination-arrow-prev {\n transform: translate(-42%, -50%) rotate(135deg);\n}\n.canvas-react-pagination-arrow-next {\n transform: translate(-58%, -50%) rotate(-45deg);\n}\n.canvas-react-pagination-button:hover .canvas-react-pagination-arrow {\n border-color: var(--canvas-react-color-primary-hover);\n}\n.canvas-react-pagination-button:disabled .canvas-react-pagination-arrow {\n opacity: 0.56;\n}\n.canvas-react-pagination-quick {\n display: inline-flex;\n align-items: center;\n gap: var(--canvas-react-pagination-gap);\n flex-wrap: nowrap;\n}\n.canvas-react-pagination-total {\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-pagination button,\n.canvas-react-pagination input {\n font-family: inherit;\n font-weight: inherit;\n font-size: inherit;\n}\n');
9510
+ styleInject('@import "../../styles/variables.css";\n.canvas-react-pagination {\n --canvas-react-pagination-control-size: 32px;\n --canvas-react-pagination-gap: 8px;\n --canvas-react-pagination-padding-inline: 10px;\n --canvas-react-control-height: var(--canvas-react-pagination-control-size);\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: var(--canvas-react-pagination-gap);\n color: var(--canvas-react-color-text);\n font-size: var(--canvas-react-font-size);\n white-space: nowrap;\n}\n.canvas-react-pagination-small {\n --canvas-react-pagination-control-size: 24px;\n --canvas-react-pagination-gap: 4px;\n --canvas-react-pagination-padding-inline: 6px;\n}\n.canvas-react-pagination > .canvas-react-select-wrap {\n flex: 0 0 auto;\n width: auto;\n height: auto;\n}\n.canvas-react-pagination > .canvas-react-select-wrap > .canvas-react-select {\n flex-shrink: 0;\n}\n.canvas-react-pagination-large {\n --canvas-react-pagination-control-size: 40px;\n --canvas-react-pagination-gap: 10px;\n --canvas-react-pagination-padding-inline: 14px;\n}\n.canvas-react-pagination-pages {\n display: inline-flex;\n align-items: center;\n gap: var(--canvas-react-pagination-gap);\n flex-wrap: nowrap;\n}\n.canvas-react-pagination-button,\n.canvas-react-pagination-page {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--canvas-react-pagination-control-size);\n height: var(--canvas-react-pagination-control-size);\n padding: 0 var(--canvas-react-pagination-padding-inline);\n border: 1px solid var(--canvas-react-color-border);\n border-radius: var(--canvas-react-radius-sm);\n background: var(--canvas-react-color-bg);\n color: var(--canvas-react-color-text);\n cursor: pointer;\n transition:\n border-color 0.2s ease,\n color 0.2s ease,\n background-color 0.2s ease;\n}\n.canvas-react-pagination-button {\n position: relative;\n width: var(--canvas-react-pagination-control-size);\n min-width: var(--canvas-react-pagination-control-size);\n padding: 0;\n}\n.canvas-react-pagination-button:hover,\n.canvas-react-pagination-page:hover {\n border-color: var(--canvas-react-color-primary-hover);\n color: var(--canvas-react-color-primary-hover);\n}\n.canvas-react-pagination-page-active {\n border-color: var(--canvas-react-color-primary);\n background: var(--canvas-react-color-bg-active);\n color: var(--canvas-react-color-primary);\n}\n.canvas-react-pagination-button:disabled,\n.canvas-react-pagination-page:disabled {\n opacity: 0.48;\n cursor: not-allowed;\n}\n.canvas-react-pagination-ellipsis {\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-pagination-arrow {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 7px;\n height: 7px;\n border-right: 1.5px solid var(--canvas-react-color-text-muted);\n border-bottom: 1.5px solid var(--canvas-react-color-text-muted);\n transition:\n border-color 0.18s ease,\n transform 0.18s ease,\n opacity 0.18s ease;\n}\n.canvas-react-pagination-arrow-prev {\n transform: translate(-42%, -50%) rotate(135deg);\n}\n.canvas-react-pagination-arrow-next {\n transform: translate(-58%, -50%) rotate(-45deg);\n}\n.canvas-react-pagination-button:hover .canvas-react-pagination-arrow {\n border-color: var(--canvas-react-color-primary-hover);\n}\n.canvas-react-pagination-button:disabled .canvas-react-pagination-arrow {\n opacity: 0.56;\n}\n.canvas-react-pagination-quick {\n display: inline-flex;\n align-items: center;\n gap: var(--canvas-react-pagination-gap);\n flex-wrap: nowrap;\n}\n.canvas-react-pagination-total {\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-pagination button,\n.canvas-react-pagination input {\n font-family: inherit;\n font-weight: inherit;\n font-size: inherit;\n}\n.canvas-react-pagination-remove-size {\n flex: 0 0 auto;\n margin-left: 6px;\n padding: 0 4px;\n border: 0;\n border-radius: var(--canvas-react-radius-sm);\n background: transparent;\n color: var(--canvas-react-color-text-muted);\n font: inherit;\n cursor: pointer;\n}\n.canvas-react-pagination-remove-size:hover,\n.canvas-react-pagination-remove-size:focus-visible {\n background: var(--canvas-react-color-bg-hover);\n color: var(--canvas-react-color-primary);\n}\n.canvas-react-pagination-remove-size:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n');
9511
+ var DEFAULT_PAGE_SIZE = 10;
9503
9512
  var DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
9504
9513
  function ReactPagination(props) {
9505
9514
  const {
9515
+ allowPageSizeInput = false,
9516
+ minPageSize = 1,
9517
+ maxPageSize = Number.MAX_SAFE_INTEGER,
9506
9518
  current,
9507
9519
  disabled,
9508
9520
  onChange,
9509
9521
  onPageSizeChange,
9510
- pageSize = 10,
9522
+ pageSize = DEFAULT_PAGE_SIZE,
9511
9523
  pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
9512
9524
  showLessItems,
9513
9525
  showQuickJumper,
@@ -9521,12 +9533,56 @@ function ReactPagination(props) {
9521
9533
  } = props;
9522
9534
  const style = { ...rootStyle, ...styles?.root };
9523
9535
  const [quickValue, setQuickValue] = useState("");
9536
+ const [customPageSizes, setCustomPageSizes] = useState([]);
9537
+ const pageSizeDraft = useRef(null);
9538
+ const sizeChangerRef = useRef(null);
9539
+ const minimum = Number.isSafeInteger(minPageSize) && minPageSize > 0 ? minPageSize : 1;
9540
+ const maximum = Number.isSafeInteger(maxPageSize) && maxPageSize > 0 ? Math.max(minimum, maxPageSize) : Number.MAX_SAFE_INTEGER;
9541
+ const defaultPageSize = Math.min(
9542
+ maximum,
9543
+ Math.max(minimum, DEFAULT_PAGE_SIZE)
9544
+ );
9545
+ const isValidPageSize = (value) => Number.isSafeInteger(value) && value >= minimum && value <= maximum;
9524
9546
  const resolvedPageSizeOptions = useMemo(() => {
9525
- if (pageSizeOptions.length === 0 || pageSizeOptions.includes(pageSize) || !Number.isSafeInteger(pageSize) || pageSize <= 0) {
9526
- return pageSizeOptions;
9547
+ const options = allowPageSizeInput ? [.../* @__PURE__ */ new Set([...pageSizeOptions, ...customPageSizes])].sort(
9548
+ (a, b) => a - b
9549
+ ) : pageSizeOptions;
9550
+ if (options.length === 0 && !allowPageSizeInput || options.includes(pageSize) || !Number.isSafeInteger(pageSize) || pageSize <= 0) {
9551
+ return options;
9552
+ }
9553
+ return [...options, pageSize].sort((left, right) => left - right);
9554
+ }, [pageSizeOptions, pageSize, customPageSizes, allowPageSizeInput]);
9555
+ const changePageSize = (value) => {
9556
+ pageSizeDraft.current = null;
9557
+ if (disabled || !isValidPageSize(value)) return;
9558
+ if (allowPageSizeInput) {
9559
+ setCustomPageSizes(
9560
+ (sizes) => sizes.includes(value) ? sizes : [...sizes, value]
9561
+ );
9527
9562
  }
9528
- return [...pageSizeOptions, pageSize].sort((left, right) => left - right);
9529
- }, [pageSizeOptions, pageSize]);
9563
+ onPageSizeChange?.(1, value);
9564
+ };
9565
+ const removeCustomPageSize = (value) => {
9566
+ if (disabled) return;
9567
+ pageSizeDraft.current = null;
9568
+ sizeChangerRef.current?.blur();
9569
+ setCustomPageSizes((sizes) => sizes.filter((size2) => size2 !== value));
9570
+ if (value === pageSize) {
9571
+ onPageSizeChange?.(1, defaultPageSize);
9572
+ }
9573
+ };
9574
+ const commitPageSizeInput = () => {
9575
+ const draft = pageSizeDraft.current;
9576
+ pageSizeDraft.current = null;
9577
+ if (!allowPageSizeInput || draft == null || !/^\d+$/.test(draft.trim()))
9578
+ return;
9579
+ changePageSize(Number(draft.trim()));
9580
+ };
9581
+ const pageSizeSelectOptions = resolvedPageSizeOptions.map((size2) => ({
9582
+ label: `${size2} / \u9875`,
9583
+ value: size2,
9584
+ ...!isValidPageSize(size2) ? { disabled: true } : {}
9585
+ }));
9530
9586
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
9531
9587
  const safeCurrent = clampPage(current, totalPages);
9532
9588
  const range = total === 0 ? [0, 0] : [
@@ -9658,26 +9714,65 @@ function ReactPagination(props) {
9658
9714
  )
9659
9715
  }
9660
9716
  ),
9661
- showSizeChanger && resolvedPageSizeOptions.length ? /* @__PURE__ */ jsx(
9717
+ showSizeChanger && (allowPageSizeInput || resolvedPageSizeOptions.length) ? /* @__PURE__ */ jsx(
9662
9718
  ReactSelect,
9663
9719
  {
9720
+ ref: sizeChangerRef,
9664
9721
  value: String(pageSize),
9665
9722
  disabled,
9723
+ "aria-label": "\u6BCF\u9875\u6761\u6570",
9724
+ allowClear: false,
9725
+ showSearch: allowPageSizeInput,
9726
+ allowCustomValue: allowPageSizeInput,
9727
+ inputMode: allowPageSizeInput ? "numeric" : void 0,
9728
+ onInputValueChange: (value) => {
9729
+ pageSizeDraft.current = value;
9730
+ },
9731
+ onKeyDown: (event) => {
9732
+ if (event.key === "Escape") pageSizeDraft.current = null;
9733
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && pageSizeDraft.current != null && filterSelectOptions(
9734
+ pageSizeSelectOptions,
9735
+ pageSizeDraft.current,
9736
+ true
9737
+ ).some((option) => !option.disabled)) {
9738
+ pageSizeDraft.current = null;
9739
+ }
9740
+ if (allowPageSizeInput && event.key === "Enter" && pageSizeDraft.current != null) {
9741
+ event.preventDefault();
9742
+ commitPageSizeInput();
9743
+ event.currentTarget.blur();
9744
+ }
9745
+ },
9746
+ onBlur: () => commitPageSizeInput(),
9666
9747
  style: {
9667
9748
  width: resolvePaginationSelectWidth(size, resolvedPageSizeOptions),
9668
9749
  ...styles?.sizeChanger
9669
9750
  },
9670
- options: resolvedPageSizeOptions.map((size2) => ({
9671
- label: `${size2} / \u9875`,
9672
- value: size2
9673
- })),
9674
- onChange: (value) => {
9675
- const nextPageSize = Number(value);
9676
- if (!Number.isFinite(nextPageSize)) {
9677
- return;
9678
- }
9679
- onPageSizeChange?.(1, nextPageSize);
9680
- }
9751
+ options: pageSizeSelectOptions,
9752
+ renderOptionExtra: (option) => {
9753
+ const value = Number(option.value);
9754
+ if (!allowPageSizeInput || pageSizeOptions.includes(value) || value === defaultPageSize)
9755
+ return null;
9756
+ return /* @__PURE__ */ jsx(
9757
+ "button",
9758
+ {
9759
+ type: "button",
9760
+ className: `${REACT_COMPONENT_PREFIX}-pagination-remove-size`,
9761
+ "aria-label": `\u5220\u9664\u6BCF\u9875 ${value} \u6761`,
9762
+ disabled,
9763
+ onMouseDown: (event) => {
9764
+ event.preventDefault();
9765
+ event.stopPropagation();
9766
+ },
9767
+ onClick: (event) => {
9768
+ event.stopPropagation();
9769
+ removeCustomPageSize(value);
9770
+ },
9771
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xD7" })
9772
+ }
9773
+ );
9774
+ },
9775
+ onChange: (value) => changePageSize(Number(value))
9681
9776
  }
9682
9777
  ) : null,
9683
9778
  showQuickJumper ? /* @__PURE__ */ jsxs(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canvas-components/react-components",
3
- "version": "0.3.28",
3
+ "version": "0.3.29",
4
4
  "description": "Shared React UI primitives for canvas-components adapters.",
5
5
  "license": "MIT",
6
6
  "type": "module",