@nocobase/client-v2 2.3.0-alpha.1 → 2.3.0-beta.11
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/es/APIClient.d.ts +12 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +12 -2
- package/es/components/form/ScanInput/zxingWasmDecoder.d.ts +9 -0
- package/es/flow/actions/linkageRules.d.ts +25 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +6 -30
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
- package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +32 -0
- package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
- package/es/flow/components/field-value-variable/index.d.ts +11 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +7 -0
- package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
- package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
- package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
- package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
- package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
- package/es/flow/models/blocks/table/utils.d.ts +1 -0
- package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
- package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +1 -0
- package/es/index.mjs +148 -145
- package/es/ui-operation/index.d.ts +15 -0
- package/es/ui-operation/ui-operation-codec.d.ts +10 -0
- package/lib/index.js +170 -167
- package/lib/locale/languageCodes.js +1 -0
- package/package.json +10 -8
- package/src/APIClient.ts +92 -0
- package/src/Application.tsx +3 -1
- package/src/__tests__/APIClient.test.tsx +58 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +7 -2
- package/src/__tests__/settings-center.test.tsx +270 -6
- package/src/acl/ACLProvider.tsx +2 -0
- package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
- package/src/collection-manager/__tests__/field-configure.test.ts +52 -0
- package/src/collection-manager/field-configure.ts +2 -2
- package/src/components/form/ScanInput/CodeScanner.tsx +64 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +107 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +294 -24
- package/src/components/form/ScanInput/__tests__/zxingWasmDecoder.test.ts +78 -0
- package/src/components/form/ScanInput/useCodeScanner.ts +281 -23
- package/src/components/form/ScanInput/zxingWasmDecoder.ts +64 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +4 -2
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +17 -0
- package/src/components/form/table/Table.tsx +81 -6
- package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +126 -8
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
- package/src/flow/actions/__tests__/validation.test.ts +48 -0
- package/src/flow/actions/linkageRules.tsx +59 -16
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +42 -23
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignValueInput.tsx +42 -621
- package/src/flow/components/FlowRoute.tsx +71 -29
- package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
- package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +335 -0
- package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +389 -0
- package/src/flow/components/field-value-variable/dateValue.ts +223 -0
- package/src/flow/components/field-value-variable/index.ts +12 -0
- package/src/flow/components/filter/VariableFilterItem.tsx +23 -6
- package/src/flow/components/filter/__tests__/VariableFilterItem.rightMetaTree.test.tsx +158 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +125 -0
- package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
- package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
- package/src/flow/index.ts +2 -0
- package/src/flow/internal/components/Markdown/DisplayMarkdown.tsx +14 -9
- package/src/flow/internal/components/Markdown/__tests__/DisplayMarkdown.test.tsx +46 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
- package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
- package/src/flow/models/base/GridModel.tsx +0 -1
- package/src/flow/models/base/PageModel/PageTabModel.tsx +67 -17
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +509 -12
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
- package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +22 -1
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +36 -50
- package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +140 -0
- package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
- package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +27 -3
- package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +129 -14
- package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.toggleFormFieldsCollapse.test.ts +29 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +359 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +32 -4
- package/src/flow/models/blocks/filter-form/fields/FieldComponentProps.tsx +1 -1
- package/src/flow/models/blocks/filter-form/fields/__tests__/FieldComponentProps.options.test.tsx +61 -0
- package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
- package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +73 -5
- package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +153 -2
- package/src/flow/models/blocks/form/__tests__/popupLinkage.test.tsx +175 -0
- package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
- package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +28 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +48 -9
- package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/blocks/table/utils.ts +7 -0
- package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +12 -3
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
- package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +29 -3
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +9 -136
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +5 -1
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +191 -3
- package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/DisplayAssociationField/DisplaySubTableFieldModel.tsx +42 -7
- package/src/flow/models/fields/DisplayAssociationField/__tests__/DisplaySubTableFieldModel.test.tsx +351 -0
- package/src/flow/models/fields/DisplayHtmlFieldModel.tsx +2 -1
- package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/RichTextFieldModel/__tests__/RichTextFieldModel.test.tsx +74 -0
- package/src/flow/models/fields/RichTextFieldModel/index.tsx +27 -6
- package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +6 -0
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +27 -2
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
- package/src/flow/models/topbar/TopbarActionModel.tsx +5 -5
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/index.ts +1 -0
- package/src/layout-manager/LayoutContentRoute.tsx +2 -1
- package/src/layout-manager/LayoutRoute.tsx +2 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
- package/src/locale/languageCodes.ts +1 -0
- package/src/settings-center/AdminSettingsLayout.tsx +23 -2
- package/src/ui-operation/index.ts +33 -0
- package/src/ui-operation/ui-operation-codec.ts +54 -0
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import { Html5Qrcode, Html5QrcodeScannerState, Html5QrcodeSupportedFormats } from 'html5-qrcode';
|
|
11
11
|
import jsQR from 'jsqr';
|
|
12
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
12
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
13
|
+
import type { RefObject } from 'react';
|
|
13
14
|
import type { CodeFormatsToSupport } from './types';
|
|
15
|
+
import { decodeQrCodeWithZxingWasm } from './zxingWasmDecoder';
|
|
14
16
|
|
|
15
17
|
type ScannerSize = {
|
|
16
18
|
width: number;
|
|
@@ -21,8 +23,8 @@ type UseCodeScannerOptions = {
|
|
|
21
23
|
enabled: boolean;
|
|
22
24
|
elementId: string;
|
|
23
25
|
formatsToSupport?: CodeFormatsToSupport;
|
|
24
|
-
scanBoxSize?: ScannerSize;
|
|
25
26
|
onScannerSizeChanged?: (size: ScannerSize) => void;
|
|
27
|
+
scanViewportRef?: RefObject<HTMLElement | null>;
|
|
26
28
|
onScanSuccess: (text: string) => void;
|
|
27
29
|
onScanFailure?: () => void;
|
|
28
30
|
onCameraStartFailure?: (error: unknown) => void;
|
|
@@ -38,7 +40,31 @@ type JsQRImageTransform = {
|
|
|
38
40
|
threshold?: number;
|
|
39
41
|
};
|
|
40
42
|
|
|
43
|
+
type QrFrameCaptureLimits = {
|
|
44
|
+
maxHeight: number;
|
|
45
|
+
maxPixels: number;
|
|
46
|
+
maxWidth: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type FocusMediaTrackCapabilities = MediaTrackCapabilities & {
|
|
50
|
+
focusMode?: string[];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type FocusMediaTrackConstraintSet = MediaTrackConstraintSet & {
|
|
54
|
+
focusMode?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
41
57
|
const QR_SCAN_IMAGE_SIZES = [3200, 2400, 1600, 1000];
|
|
58
|
+
const LIVE_QR_SCAN_INTERVAL = 120;
|
|
59
|
+
const IOS_ZXING_SCAN_INTERVAL = 200;
|
|
60
|
+
const LIVE_QR_SCAN_MAX_WIDTH = 960;
|
|
61
|
+
const LIVE_QR_SCAN_MAX_HEIGHT = 540;
|
|
62
|
+
const LIVE_QR_SCAN_MAX_PIXELS = LIVE_QR_SCAN_MAX_WIDTH * 420;
|
|
63
|
+
const IOS_ZXING_SCAN_LIMITS: QrFrameCaptureLimits = {
|
|
64
|
+
maxHeight: 720,
|
|
65
|
+
maxPixels: 720 * 960,
|
|
66
|
+
maxWidth: 1280,
|
|
67
|
+
};
|
|
42
68
|
const QR_SCAN_IMAGE_TRANSFORMS: JsQRImageTransform[] = [
|
|
43
69
|
{},
|
|
44
70
|
{ contrast: 3, threshold: 105 },
|
|
@@ -66,8 +92,170 @@ export const DEFAULT_CODE_FORMATS: CodeFormatsToSupport = [
|
|
|
66
92
|
|
|
67
93
|
export function getCodeScanBoxSize(width: number, height: number) {
|
|
68
94
|
return {
|
|
69
|
-
width: Math.floor(Math.min(width *
|
|
70
|
-
height: Math.floor(Math.min(height *
|
|
95
|
+
width: Math.floor(Math.min((width * 90) / 100, 1152)),
|
|
96
|
+
height: Math.floor(Math.min((height * 70) / 100, 540)),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function isIOSBrowser() {
|
|
101
|
+
return (
|
|
102
|
+
/iPad|iPhone|iPod/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getVisibleVideoFrameRegion(video: HTMLVideoElement, scanViewport: Element) {
|
|
107
|
+
const rect = video.getBoundingClientRect();
|
|
108
|
+
const viewportRect = scanViewport.getBoundingClientRect();
|
|
109
|
+
const visibleLeft = Math.max(viewportRect.left, rect.left);
|
|
110
|
+
const visibleTop = Math.max(viewportRect.top, rect.top);
|
|
111
|
+
const visibleRight = Math.min(viewportRect.right, rect.right);
|
|
112
|
+
const visibleBottom = Math.min(viewportRect.bottom, rect.bottom);
|
|
113
|
+
if (!rect.width || !rect.height || visibleRight <= visibleLeft || visibleBottom <= visibleTop) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const sourceX = Math.max(0, Math.floor(((visibleLeft - rect.left) / rect.width) * video.videoWidth));
|
|
118
|
+
const sourceY = Math.max(0, Math.floor(((visibleTop - rect.top) / rect.height) * video.videoHeight));
|
|
119
|
+
return {
|
|
120
|
+
x: sourceX,
|
|
121
|
+
y: sourceY,
|
|
122
|
+
width: Math.min(
|
|
123
|
+
video.videoWidth - sourceX,
|
|
124
|
+
Math.ceil(((visibleRight - visibleLeft) / rect.width) * video.videoWidth),
|
|
125
|
+
),
|
|
126
|
+
height: Math.min(
|
|
127
|
+
video.videoHeight - sourceY,
|
|
128
|
+
Math.ceil(((visibleBottom - visibleTop) / rect.height) * video.videoHeight),
|
|
129
|
+
),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getQrVideoFrameImageData(
|
|
134
|
+
video: HTMLVideoElement,
|
|
135
|
+
canvas: HTMLCanvasElement,
|
|
136
|
+
scanViewport?: Element,
|
|
137
|
+
limits: QrFrameCaptureLimits = {
|
|
138
|
+
maxHeight: LIVE_QR_SCAN_MAX_HEIGHT,
|
|
139
|
+
maxPixels: LIVE_QR_SCAN_MAX_PIXELS,
|
|
140
|
+
maxWidth: LIVE_QR_SCAN_MAX_WIDTH,
|
|
141
|
+
},
|
|
142
|
+
) {
|
|
143
|
+
if (!video.videoWidth || !video.videoHeight || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const viewfinderWidth = video.clientWidth || video.videoWidth;
|
|
148
|
+
const viewfinderHeight = video.clientHeight || video.videoHeight;
|
|
149
|
+
const scanBoxSize = getCodeScanBoxSize(viewfinderWidth, viewfinderHeight);
|
|
150
|
+
const scanBoxRegion = {
|
|
151
|
+
width: Math.min(video.videoWidth, Math.floor(scanBoxSize.width * (video.videoWidth / viewfinderWidth))),
|
|
152
|
+
height: Math.min(video.videoHeight, Math.floor(scanBoxSize.height * (video.videoHeight / viewfinderHeight))),
|
|
153
|
+
};
|
|
154
|
+
const sourceRegion = scanViewport
|
|
155
|
+
? getVisibleVideoFrameRegion(video, scanViewport)
|
|
156
|
+
: {
|
|
157
|
+
x: Math.floor((video.videoWidth - scanBoxRegion.width) / 2),
|
|
158
|
+
y: Math.floor((video.videoHeight - scanBoxRegion.height) / 2),
|
|
159
|
+
...scanBoxRegion,
|
|
160
|
+
};
|
|
161
|
+
if (!sourceRegion) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const maxWidth = scanViewport && sourceRegion.height > sourceRegion.width ? limits.maxHeight : limits.maxWidth;
|
|
166
|
+
const maxHeight = scanViewport && sourceRegion.height > sourceRegion.width ? limits.maxWidth : limits.maxHeight;
|
|
167
|
+
const pixelScale = scanViewport ? Math.sqrt(limits.maxPixels / (sourceRegion.width * sourceRegion.height)) : 1;
|
|
168
|
+
const targetScale = Math.min(1, maxWidth / sourceRegion.width, maxHeight / sourceRegion.height, pixelScale);
|
|
169
|
+
const targetWidth = Math.max(1, Math.floor(sourceRegion.width * targetScale));
|
|
170
|
+
const targetHeight = Math.max(1, Math.floor(sourceRegion.height * targetScale));
|
|
171
|
+
if (canvas.width !== targetWidth) {
|
|
172
|
+
canvas.width = targetWidth;
|
|
173
|
+
}
|
|
174
|
+
if (canvas.height !== targetHeight) {
|
|
175
|
+
canvas.height = targetHeight;
|
|
176
|
+
}
|
|
177
|
+
const context = canvas.getContext('2d', { willReadFrequently: true });
|
|
178
|
+
if (!context) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
context.drawImage(
|
|
183
|
+
video,
|
|
184
|
+
sourceRegion.x,
|
|
185
|
+
sourceRegion.y,
|
|
186
|
+
sourceRegion.width,
|
|
187
|
+
sourceRegion.height,
|
|
188
|
+
0,
|
|
189
|
+
0,
|
|
190
|
+
targetWidth,
|
|
191
|
+
targetHeight,
|
|
192
|
+
);
|
|
193
|
+
return context.getImageData(0, 0, targetWidth, targetHeight);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function scanQrVideoFrame(video: HTMLVideoElement, canvas: HTMLCanvasElement, scanViewport?: Element) {
|
|
197
|
+
const imageData = getQrVideoFrameImageData(video, canvas, scanViewport);
|
|
198
|
+
if (!imageData) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
return jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: 'dontInvert' })?.data;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function scanQrVideoFrameWithZxingWasm(
|
|
205
|
+
video: HTMLVideoElement,
|
|
206
|
+
canvas: HTMLCanvasElement,
|
|
207
|
+
scanViewport: Element,
|
|
208
|
+
) {
|
|
209
|
+
const imageData = getQrVideoFrameImageData(video, canvas, scanViewport, IOS_ZXING_SCAN_LIMITS);
|
|
210
|
+
if (!imageData) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
return decodeQrCodeWithZxingWasm(imageData);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function startLiveQrScan(
|
|
217
|
+
elementId: string,
|
|
218
|
+
onScanSuccess: (text: string) => void,
|
|
219
|
+
scanViewportRef?: RefObject<HTMLElement | null>,
|
|
220
|
+
) {
|
|
221
|
+
const canvas = document.createElement('canvas');
|
|
222
|
+
const useVisiblePreview = isIOSBrowser();
|
|
223
|
+
let timer: number | undefined;
|
|
224
|
+
let stopped = false;
|
|
225
|
+
|
|
226
|
+
const scan = async () => {
|
|
227
|
+
if (stopped) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const video = document.getElementById(elementId)?.querySelector('video');
|
|
231
|
+
const scanViewport = useVisiblePreview ? scanViewportRef?.current : undefined;
|
|
232
|
+
if (video) {
|
|
233
|
+
let decodedText: string | undefined;
|
|
234
|
+
try {
|
|
235
|
+
decodedText =
|
|
236
|
+
useVisiblePreview && scanViewport
|
|
237
|
+
? await scanQrVideoFrameWithZxingWasm(video, canvas, scanViewport)
|
|
238
|
+
: scanQrVideoFrame(video, canvas);
|
|
239
|
+
} catch {
|
|
240
|
+
// Keep the existing scanners active when the optional WASM decoder cannot load or decode a frame.
|
|
241
|
+
}
|
|
242
|
+
if (decodedText && !stopped) {
|
|
243
|
+
stopped = true;
|
|
244
|
+
onScanSuccess(decodedText);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (!stopped) {
|
|
249
|
+
timer = window.setTimeout(scan, useVisiblePreview ? IOS_ZXING_SCAN_INTERVAL : LIVE_QR_SCAN_INTERVAL);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
timer = window.setTimeout(scan, 0);
|
|
254
|
+
return () => {
|
|
255
|
+
stopped = true;
|
|
256
|
+
if (timer !== undefined) {
|
|
257
|
+
window.clearTimeout(timer);
|
|
258
|
+
}
|
|
71
259
|
};
|
|
72
260
|
}
|
|
73
261
|
|
|
@@ -92,6 +280,19 @@ async function stopScanner(scanner?: Html5Qrcode, options: { clear?: boolean } =
|
|
|
92
280
|
}
|
|
93
281
|
}
|
|
94
282
|
|
|
283
|
+
async function enableContinuousFocus(scanner: Html5Qrcode) {
|
|
284
|
+
try {
|
|
285
|
+
const capabilities = scanner.getRunningTrackCapabilities() as FocusMediaTrackCapabilities;
|
|
286
|
+
if (!capabilities.focusMode?.includes('continuous')) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const focusConstraints: FocusMediaTrackConstraintSet = { focusMode: 'continuous' };
|
|
290
|
+
await scanner.applyVideoConstraints({ advanced: [focusConstraints] });
|
|
291
|
+
} catch {
|
|
292
|
+
// Some browsers expose incomplete camera capability APIs. Scanning should continue without explicit focus control.
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
95
296
|
function isSafariBrowser() {
|
|
96
297
|
const { userAgent, vendor } = navigator;
|
|
97
298
|
return /Apple/i.test(vendor) && /Safari/i.test(userAgent) && !/CriOS|FxiOS|EdgiOS|Chrome/i.test(userAgent);
|
|
@@ -216,40 +417,96 @@ export function useCodeScanner({
|
|
|
216
417
|
enabled,
|
|
217
418
|
elementId,
|
|
218
419
|
formatsToSupport,
|
|
219
|
-
scanBoxSize,
|
|
220
420
|
onScannerSizeChanged,
|
|
421
|
+
scanViewportRef,
|
|
221
422
|
onScanSuccess,
|
|
222
423
|
onScanFailure,
|
|
223
424
|
onCameraStartFailure,
|
|
224
425
|
}: UseCodeScannerOptions) {
|
|
225
426
|
const [scanner, setScanner] = useState<Html5Qrcode>();
|
|
427
|
+
const liveQrScanStopRef = useRef<() => void>();
|
|
428
|
+
const scanSucceededRef = useRef(false);
|
|
429
|
+
const scanSessionRef = useRef(0);
|
|
430
|
+
|
|
431
|
+
const stopLiveQrScan = useCallback(() => {
|
|
432
|
+
liveQrScanStopRef.current?.();
|
|
433
|
+
liveQrScanStopRef.current = undefined;
|
|
434
|
+
}, []);
|
|
435
|
+
|
|
436
|
+
const cancelActiveScan = useCallback(() => {
|
|
437
|
+
scanSessionRef.current += 1;
|
|
438
|
+
stopLiveQrScan();
|
|
439
|
+
}, [stopLiveQrScan]);
|
|
440
|
+
|
|
441
|
+
const reportScanSuccess = useCallback(
|
|
442
|
+
(text: string) => {
|
|
443
|
+
if (scanSucceededRef.current) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
scanSucceededRef.current = true;
|
|
447
|
+
stopLiveQrScan();
|
|
448
|
+
onScanSuccess(text);
|
|
449
|
+
},
|
|
450
|
+
[onScanSuccess, stopLiveQrScan],
|
|
451
|
+
);
|
|
226
452
|
|
|
227
453
|
const startScanCamera = useCallback(
|
|
228
454
|
async (scannerInstance: Html5Qrcode) => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
455
|
+
cancelActiveScan();
|
|
456
|
+
const scanSession = scanSessionRef.current;
|
|
457
|
+
scanSucceededRef.current = false;
|
|
458
|
+
try {
|
|
459
|
+
await scannerInstance.start(
|
|
460
|
+
{ facingMode: 'environment' },
|
|
461
|
+
{
|
|
462
|
+
fps: 8,
|
|
463
|
+
disableFlip: false,
|
|
464
|
+
videoConstraints: {
|
|
465
|
+
facingMode: { ideal: 'environment' },
|
|
466
|
+
width: { ideal: 1920 },
|
|
467
|
+
height: { ideal: 1080 },
|
|
468
|
+
frameRate: { ideal: 30 },
|
|
469
|
+
},
|
|
470
|
+
qrbox(width, height) {
|
|
471
|
+
onScannerSizeChanged?.({ width, height });
|
|
472
|
+
return clampCodeScanBoxSize(getCodeScanBoxSize(width, height), width, height);
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
(decodedText) => {
|
|
476
|
+
reportScanSuccess(decodedText);
|
|
236
477
|
},
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
478
|
+
undefined,
|
|
479
|
+
);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
if (scanSession !== scanSessionRef.current) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
if (scanSession !== scanSessionRef.current) {
|
|
487
|
+
try {
|
|
488
|
+
await stopScanner(scannerInstance, { clear: true });
|
|
489
|
+
} catch {
|
|
490
|
+
// The scanner may already have been cleared by the canceled session cleanup.
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (shouldScanQrWithJsQR(formatsToSupport)) {
|
|
495
|
+
liveQrScanStopRef.current = startLiveQrScan(elementId, reportScanSuccess, scanViewportRef);
|
|
496
|
+
}
|
|
497
|
+
await enableContinuousFocus(scannerInstance);
|
|
243
498
|
},
|
|
244
|
-
[
|
|
499
|
+
[cancelActiveScan, elementId, formatsToSupport, onScannerSizeChanged, reportScanSuccess, scanViewportRef],
|
|
245
500
|
);
|
|
246
501
|
|
|
247
502
|
const startScanFile = useCallback(
|
|
248
503
|
async (file: File) => {
|
|
504
|
+
cancelActiveScan();
|
|
505
|
+
scanSucceededRef.current = false;
|
|
249
506
|
if (isSafariBrowser() && shouldScanQrWithJsQR(formatsToSupport)) {
|
|
250
507
|
try {
|
|
251
508
|
const decodedText = await scanFileWithJsQR(file, formatsToSupport);
|
|
252
|
-
|
|
509
|
+
reportScanSuccess(decodedText);
|
|
253
510
|
return;
|
|
254
511
|
} catch {
|
|
255
512
|
// Fall through to html5-qrcode so barcode uploads still work in Safari.
|
|
@@ -263,13 +520,13 @@ export function useCodeScanner({
|
|
|
263
520
|
await stopScanner(scanner);
|
|
264
521
|
try {
|
|
265
522
|
const result = await scanner.scanFileV2(file, false);
|
|
266
|
-
|
|
523
|
+
reportScanSuccess(result.decodedText);
|
|
267
524
|
} catch {
|
|
268
525
|
onScanFailure?.();
|
|
269
526
|
await startScanCamera(scanner);
|
|
270
527
|
}
|
|
271
528
|
},
|
|
272
|
-
[formatsToSupport, onScanFailure,
|
|
529
|
+
[cancelActiveScan, formatsToSupport, onScanFailure, reportScanSuccess, scanner, startScanCamera],
|
|
273
530
|
);
|
|
274
531
|
|
|
275
532
|
useEffect(() => {
|
|
@@ -291,9 +548,10 @@ export function useCodeScanner({
|
|
|
291
548
|
});
|
|
292
549
|
|
|
293
550
|
return () => {
|
|
551
|
+
cancelActiveScan();
|
|
294
552
|
stopScanner(scannerInstance, { clear: true }).catch(() => undefined);
|
|
295
553
|
};
|
|
296
|
-
}, [elementId, enabled, formatsToSupport, onCameraStartFailure, onScanFailure, startScanCamera]);
|
|
554
|
+
}, [cancelActiveScan, elementId, enabled, formatsToSupport, onCameraStartFailure, onScanFailure, startScanCamera]);
|
|
297
555
|
|
|
298
556
|
return {
|
|
299
557
|
startScanFile,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
let decoderPromise: Promise<typeof import('zxing-wasm/reader')> | undefined;
|
|
11
|
+
|
|
12
|
+
async function loadDecoder() {
|
|
13
|
+
if (!decoderPromise) {
|
|
14
|
+
decoderPromise = Promise.all([import('zxing-wasm/reader'), import('zxing-wasm/reader/zxing_reader.wasm')])
|
|
15
|
+
.then(async ([decoder, { default: wasmUrl }]) => {
|
|
16
|
+
try {
|
|
17
|
+
await decoder.prepareZXingModule({
|
|
18
|
+
fireImmediately: true,
|
|
19
|
+
overrides: { locateFile: () => wasmUrl },
|
|
20
|
+
});
|
|
21
|
+
} catch (error) {
|
|
22
|
+
decoder.purgeZXingModule();
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
return decoder;
|
|
26
|
+
})
|
|
27
|
+
.catch((error: unknown) => {
|
|
28
|
+
decoderPromise = undefined;
|
|
29
|
+
throw error;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return decoderPromise;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function decodeQrCodeWithZxingWasm(imageData: ImageData) {
|
|
36
|
+
const decoder = await loadDecoder();
|
|
37
|
+
const results = await decoder.readBarcodes(imageData, {
|
|
38
|
+
binarizer: 'GlobalHistogram',
|
|
39
|
+
downscaleThreshold: 300,
|
|
40
|
+
formats: ['QRCode'],
|
|
41
|
+
maxNumberOfSymbols: 1,
|
|
42
|
+
tryDenoise: true,
|
|
43
|
+
tryDownscale: true,
|
|
44
|
+
tryHarder: true,
|
|
45
|
+
tryInvert: true,
|
|
46
|
+
tryRotate: true,
|
|
47
|
+
});
|
|
48
|
+
if (results[0]?.text) {
|
|
49
|
+
return results[0].text;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const fallbackResults = await decoder.readBarcodes(imageData, {
|
|
53
|
+
binarizer: 'LocalAverage',
|
|
54
|
+
downscaleThreshold: 300,
|
|
55
|
+
formats: ['QRCode'],
|
|
56
|
+
maxNumberOfSymbols: 1,
|
|
57
|
+
tryDenoise: true,
|
|
58
|
+
tryDownscale: true,
|
|
59
|
+
tryHarder: true,
|
|
60
|
+
tryInvert: true,
|
|
61
|
+
tryRotate: true,
|
|
62
|
+
});
|
|
63
|
+
return fallbackResults[0]?.text;
|
|
64
|
+
}
|
|
@@ -130,8 +130,10 @@ export const CollectionFilterItem: FC<CollectionFilterItemProps> = observer(
|
|
|
130
130
|
[operatorOptions, operator],
|
|
131
131
|
);
|
|
132
132
|
|
|
133
|
-
const handleFieldChange = (next
|
|
134
|
-
|
|
133
|
+
const handleFieldChange = (next?: (string | number)[]) => {
|
|
134
|
+
// rc-cascader emits `undefined` when its clear icon is clicked in single-select mode, despite its public type
|
|
135
|
+
// declaring an array. Treat that runtime value as an empty path so clearing the field also resets the row.
|
|
136
|
+
const path = (next ?? []).map(String);
|
|
135
137
|
props.value.path = path.join('.');
|
|
136
138
|
const leaf = findOptionByPath(options, path);
|
|
137
139
|
props.value.operator = leaf?.operators?.[0]?.value || '';
|
|
@@ -104,6 +104,23 @@ describe('CollectionFilterItem', () => {
|
|
|
104
104
|
});
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
it('clears the filter row when the selected field is cleared', async () => {
|
|
108
|
+
const value = observable({ path: 'username', operator: '$eq', value: 'alice' });
|
|
109
|
+
const collection = buildStubCollection([{ name: 'username', title: 'Username' }]);
|
|
110
|
+
|
|
111
|
+
const { container } = render(<CollectionFilterItem value={value} collection={collection} />);
|
|
112
|
+
|
|
113
|
+
const clear = container.querySelector('.ant-select-clear');
|
|
114
|
+
if (!clear) throw new Error('expected Cascader clear control to be rendered');
|
|
115
|
+
fireEvent.mouseDown(clear);
|
|
116
|
+
|
|
117
|
+
await waitFor(() => {
|
|
118
|
+
expect(value.path).toBe('');
|
|
119
|
+
expect(value.operator).toBe('');
|
|
120
|
+
expect(value.value).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
107
124
|
it('clears value when the operator changes', async () => {
|
|
108
125
|
const value = observable({ path: 'username', operator: '$eq', value: 'alice' });
|
|
109
126
|
const collection = buildStubCollection([{ name: 'username', title: 'Username' }]);
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import { DragOverlay, type DragEndEvent, type DragStartEvent } from '@dnd-kit/core';
|
|
11
11
|
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
12
|
-
import { cx } from '@emotion/css';
|
|
12
|
+
import { css, cx, injectGlobal } from '@emotion/css';
|
|
13
13
|
import { DndProvider } from '@nocobase/flow-engine';
|
|
14
14
|
import { useMemoizedFn } from 'ahooks';
|
|
15
|
-
import { Table as AntdTable, type TableProps as AntdTableProps } from 'antd';
|
|
16
|
-
import type { ColumnsType, ColumnType, GetRowKey } from 'antd/es/table/interface';
|
|
15
|
+
import { Table as AntdTable, theme, type TableProps as AntdTableProps } from 'antd';
|
|
16
|
+
import type { ColumnsType, ColumnGroupType, ColumnType, GetRowKey } from 'antd/es/table/interface';
|
|
17
17
|
import type { RenderedCell } from 'rc-table/lib/interface';
|
|
18
18
|
import React, { useMemo, useState } from 'react';
|
|
19
19
|
import { SortableRow, SortHandle } from './dnd/SortableRow';
|
|
@@ -24,6 +24,20 @@ import { readRowKey, snapshotSourceRow, type RowKey, type RowSnapshot } from './
|
|
|
24
24
|
|
|
25
25
|
type RowSelectionRenderCellResult<RecordType> = React.ReactNode | RenderedCell<RecordType>;
|
|
26
26
|
|
|
27
|
+
const DEFAULT_COLUMN_CONTENT_CLASS_NAME = 'nb-table-default-column-content';
|
|
28
|
+
const DEFAULT_COLUMN_CONTENT_MAX_WIDTH_PROPERTY = '--nb-table-default-column-content-max-width';
|
|
29
|
+
|
|
30
|
+
// Zero specificity lets caller classes remain authoritative regardless of stylesheet insertion order. The max-width
|
|
31
|
+
// value itself comes from a token-derived private CSS variable set by each Table instance.
|
|
32
|
+
injectGlobal`
|
|
33
|
+
:where(.${DEFAULT_COLUMN_CONTENT_CLASS_NAME}) {
|
|
34
|
+
max-width: var(${DEFAULT_COLUMN_CONTENT_MAX_WIDTH_PROPERTY});
|
|
35
|
+
white-space: normal;
|
|
36
|
+
overflow-wrap: anywhere;
|
|
37
|
+
word-break: break-word;
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
27
41
|
/**
|
|
28
42
|
* Default initial page size for `Table`. Exposed so consumers can seed their
|
|
29
43
|
* controlled `pageSize` state with the same value the component would use if
|
|
@@ -50,6 +64,54 @@ function isRenderedCell<RecordType>(value: unknown): value is RenderedCell<Recor
|
|
|
50
64
|
return typeof value === 'object' && value !== null && !React.isValidElement(value) && 'children' in value;
|
|
51
65
|
}
|
|
52
66
|
|
|
67
|
+
function isColumnGroup<RecordType>(
|
|
68
|
+
column: ColumnGroupType<RecordType> | ColumnType<RecordType>,
|
|
69
|
+
): column is ColumnGroupType<RecordType> {
|
|
70
|
+
return 'children' in column && Array.isArray(column.children) && column.children.length > 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function addDefaultColumnContentClassName<RecordType>(
|
|
74
|
+
columns: ColumnsType<RecordType>,
|
|
75
|
+
defaultClassName: string,
|
|
76
|
+
): ColumnsType<RecordType> {
|
|
77
|
+
return columns.map((column) => {
|
|
78
|
+
if (column === AntdTable.EXPAND_COLUMN || column === AntdTable.SELECTION_COLUMN) {
|
|
79
|
+
return column;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isColumnGroup(column)) {
|
|
83
|
+
return {
|
|
84
|
+
...column,
|
|
85
|
+
children: addDefaultColumnContentClassName(column.children, defaultClassName),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (column.width !== undefined || column.ellipsis || column.fixed) {
|
|
90
|
+
return column;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const originalOnCell = column.onCell;
|
|
94
|
+
const originalOnHeaderCell = column.onHeaderCell;
|
|
95
|
+
return {
|
|
96
|
+
...column,
|
|
97
|
+
onCell: (record, index) => {
|
|
98
|
+
const cellProps = originalOnCell?.(record, index) ?? {};
|
|
99
|
+
return {
|
|
100
|
+
...cellProps,
|
|
101
|
+
className: cx(defaultClassName, cellProps.className),
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
onHeaderCell: (headerColumn) => {
|
|
105
|
+
const cellProps = originalOnHeaderCell?.(headerColumn) ?? {};
|
|
106
|
+
return {
|
|
107
|
+
...cellProps,
|
|
108
|
+
className: cx(defaultClassName, cellProps.className),
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
53
115
|
export interface TableProps<RecordType extends object = any> extends AntdTableProps<RecordType> {
|
|
54
116
|
/**
|
|
55
117
|
* Required so drag-sort, hover-swap and row identity work. Accepts the same
|
|
@@ -108,6 +170,7 @@ export interface TableProps<RecordType extends object = any> extends AntdTablePr
|
|
|
108
170
|
* plus the index swap, so it is safe as the default table on any page.
|
|
109
171
|
*/
|
|
110
172
|
export function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
|
|
173
|
+
const { token } = theme.useToken();
|
|
111
174
|
const {
|
|
112
175
|
rowKey,
|
|
113
176
|
showIndex = true,
|
|
@@ -143,6 +206,17 @@ export function Table<RecordType extends object = any>(props: TableProps<RecordT
|
|
|
143
206
|
const showHandleInSelection = isDraggable && showSortHandle && !!rowSelection;
|
|
144
207
|
const showStandaloneHandleColumn = isDraggable && showSortHandle && !rowSelection;
|
|
145
208
|
|
|
209
|
+
const defaultColumnContentClassName = useMemo(
|
|
210
|
+
() =>
|
|
211
|
+
cx(
|
|
212
|
+
DEFAULT_COLUMN_CONTENT_CLASS_NAME,
|
|
213
|
+
css`
|
|
214
|
+
${DEFAULT_COLUMN_CONTENT_MAX_WIDTH_PROPERTY}: ${token.screenXS - token.paddingXL * 3 + token.padding * 2}px;
|
|
215
|
+
`,
|
|
216
|
+
),
|
|
217
|
+
[token.padding, token.paddingXL, token.screenXS],
|
|
218
|
+
);
|
|
219
|
+
|
|
146
220
|
const itemKeys = useMemo<string[]>(() => {
|
|
147
221
|
if (!isDraggable || !dataSource) return [];
|
|
148
222
|
return dataSource
|
|
@@ -191,15 +265,16 @@ export function Table<RecordType extends object = any>(props: TableProps<RecordT
|
|
|
191
265
|
// selection cell — see `augmentedRowSelection` below.
|
|
192
266
|
const augmentedColumns = useMemo<ColumnsType<RecordType>>(() => {
|
|
193
267
|
const baseColumns: ColumnsType<RecordType> = columns ?? [];
|
|
194
|
-
|
|
268
|
+
const styledColumns = addDefaultColumnContentClassName(baseColumns, defaultColumnContentClassName);
|
|
269
|
+
if (!showStandaloneHandleColumn) return styledColumns;
|
|
195
270
|
const handleColumn: ColumnType<RecordType> = {
|
|
196
271
|
key: '__sort__',
|
|
197
272
|
width: sortHandleColumnWidth,
|
|
198
273
|
align: 'center',
|
|
199
274
|
render: () => <SortHandle />,
|
|
200
275
|
};
|
|
201
|
-
return [handleColumn, ...
|
|
202
|
-
}, [columns, showStandaloneHandleColumn, sortHandleColumnWidth]);
|
|
276
|
+
return [handleColumn, ...styledColumns];
|
|
277
|
+
}, [columns, defaultColumnContentClassName, showStandaloneHandleColumn, sortHandleColumnWidth]);
|
|
203
278
|
|
|
204
279
|
const augmentedRowSelection = useMemo(() => {
|
|
205
280
|
if (!rowSelection) return rowSelection;
|