feffery_antd_components 0.4.2 → 0.4.3-rc1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feffery_antd_components",
3
- "version": "0.4.2",
3
+ "version": "0.4.3-rc1",
4
4
  "description": "Best implementation of Antd components in Plotly Dash.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  "@formily/react": "^2.3.1",
34
34
  "@plotly/webpack-dash-dynamic-import": "^1.3.0",
35
35
  "ahooks": "^3.7.0",
36
- "antd": "^5.27.1",
36
+ "antd": "^5.27.3",
37
37
  "antd-img-crop": "^4.2.3",
38
38
  "color": "^4.2.3",
39
39
  "dayjs": "^1.11.13",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feffery_antd_components",
3
- "version": "0.4.2",
3
+ "version": "0.4.3-rc1",
4
4
  "description": "Best implementation of Antd components in Plotly Dash.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  "@formily/react": "^2.3.1",
34
34
  "@plotly/webpack-dash-dynamic-import": "^1.3.0",
35
35
  "ahooks": "^3.7.0",
36
- "antd": "^5.27.1",
36
+ "antd": "^5.27.3",
37
37
  "antd-img-crop": "^4.2.3",
38
38
  "color": "^4.2.3",
39
39
  "dayjs": "^1.11.13",
@@ -15,7 +15,7 @@ Keyword arguments:
15
15
  - `id` (String; optional): 组件唯一id
16
16
  - `aria-*` (String; optional): `aria-*`格式属性通配
17
17
  - `className` (String | Dict; optional): 当前组件css类名,支持[动态css](/advanced-classname)
18
- - `colon` (Bool; optional): 当`layput='horizontal'`时,控制是否在表单项标签部分末尾添加冒号,优先级高于所属`AntdForm`中的`colon`参数
18
+ - `colon` (Bool; optional): 当`layout='horizontal'`时,控制是否在表单项标签部分末尾添加冒号,优先级高于所属`AntdForm`中的`colon`参数
19
19
  - `data-*` (String; optional): `data-*`格式属性通配
20
20
  - `extra` (a list of or a singular dash component, string or number; optional): 组件型,当前表单项额外提示信息
21
21
  - `hasFeedback` (Bool; optional): 与`validateStatus`设定的状态对应,用于控制是否显示额外的状态图标
@@ -398,8 +398,9 @@ Those elements have the following types:
398
398
  - `columns` (Array of Strings; optional): 监听排序涉及的字段`dataIndex`信息
399
399
  - `orders` (Array of a value equal to: 'ascend', 'descend's; optional): 监听排序涉及的字段对应排序方式,其中`'ascend'`表示升序,`'descend'`表示降序
400
400
  - `sticky` (optional): 配置粘性表头相关功能
401
- 默认值:`false`. sticky has the following type: Bool | lists containing elements 'offsetHeader', 'offsetScroll'.
401
+ 默认值:`false`. sticky has the following type: Bool | lists containing elements 'belowSelector', 'offsetHeader', 'offsetScroll'.
402
402
  Those elements have the following types:
403
+ - `belowSelector` (String | Array of Strings; optional): 粘性表头附着目标元素对应的选择器规则字符串,设置后,粘性表头激活后将附着在目标元素下方
403
404
  - `offsetHeader` (Real; optional): 粘性表头竖直方向上的像素偏移量
404
405
  - `offsetScroll` (Real; optional): 粘性表头底部横向滚动条竖直方向上的像素偏移量
405
406
  - `style` (Dict; optional): 当前组件css样式
@@ -1090,6 +1090,13 @@ AntdTable.propTypes = {
1090
1090
  sticky: PropTypes.oneOfType([
1091
1091
  PropTypes.bool,
1092
1092
  PropTypes.exact({
1093
+ /**
1094
+ * 粘性表头附着目标元素对应的选择器规则字符串,设置后,粘性表头激活后将附着在目标元素下方
1095
+ */
1096
+ belowSelector: PropTypes.oneOfType([
1097
+ PropTypes.string,
1098
+ PropTypes.arrayOf(PropTypes.string)
1099
+ ]),
1093
1100
  /**
1094
1101
  * 粘性表头竖直方向上的像素偏移量
1095
1102
  */
@@ -136,7 +136,7 @@ AntdFormItem.propTypes = {
136
136
  }),
137
137
 
138
138
  /**
139
- * 当`layput='horizontal'`时,控制是否在表单项标签部分末尾添加冒号,优先级高于所属`AntdForm`中的`colon`参数
139
+ * 当`layout='horizontal'`时,控制是否在表单项标签部分末尾添加冒号,优先级高于所属`AntdForm`中的`colon`参数
140
140
  */
141
141
  colon: PropTypes.bool,
142
142
 
@@ -48,6 +48,7 @@ import { isNumber, isEqual, isString, isBoolean, isEmpty, omitBy, isUndefined }
48
48
  import { pickBy } from 'ramda';
49
49
  import { str2Locale, locale2text } from '../components/locales.react';
50
50
  import { useLoading } from '../components/utils';
51
+ import useStickyOffset from '../hooks/useStickyOffset';
51
52
  // 上下文
52
53
  import PropsContext from '../contexts/PropsContext';
53
54
  // 参数类型
@@ -232,6 +233,18 @@ const AntdTable = (props) => {
232
233
  const [searchText, setSearchText] = useState('');
233
234
  const [searchedColumn, setSearchedColumn] = useState('');
234
235
 
236
+ const stickyObj = (sticky && typeof sticky === 'object') ? sticky : {};
237
+ const belowSelector = stickyObj.belowSelector ?? undefined;
238
+ const offsetHeader = Number(stickyObj.offsetHeader || 0);
239
+ const autoMeasuredOffset = belowSelector ? useStickyOffset({ selector: belowSelector, extra: offsetHeader }) : offsetHeader;
240
+ const { belowSelector: _rm1,
241
+ offsetHeader: _rm2,
242
+ ...stickyRest
243
+ } = stickyObj;
244
+ const computedSticky = sticky === true || belowSelector || (sticky && typeof sticky === 'object')
245
+ ? { ...stickyRest, offsetHeader: autoMeasuredOffset }
246
+ : undefined;
247
+
235
248
  const onPageChange = (pagination, filter, sorter, currentData) => {
236
249
 
237
250
  // 当本次事件由翻页操作引发时
@@ -2011,7 +2024,7 @@ const AntdTable = (props) => {
2011
2024
  tableLayout={tableLayout}
2012
2025
  size={size}
2013
2026
  rowSelection={rowSelection}
2014
- sticky={sticky}
2027
+ sticky={computedSticky}
2015
2028
  pagination={
2016
2029
  // 确保pagination=false生效
2017
2030
  pagination &&
@@ -2115,8 +2128,8 @@ export default React.memo(
2115
2128
  .filter(key => !isEqual(prevProps[key], nextProps[key]))
2116
2129
 
2117
2130
  // 特殊处理:
2118
- // 当recentlySelectValue发生变动时,阻止本次重绘
2119
- if (changedProps.includes('recentlySelectValue')) {
2131
+ // 当recentlySelectValue发生变动且不涉及data变动时时,阻止本次重绘
2132
+ if (changedProps.includes('recentlySelectValue') && !changedProps.includes('data')) {
2120
2133
  return true;
2121
2134
  }
2122
2135
 
@@ -0,0 +1,115 @@
1
+ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+
3
+ function resolveNodes(selector) {
4
+ const sels = Array.isArray(selector) ? selector : [selector];
5
+ const nodes = [];
6
+ if (typeof document === 'undefined') return nodes;
7
+ for (const sel of sels) {
8
+ if (!sel) continue;
9
+ const first = document.querySelector(sel);
10
+ if (first) nodes.push(first);
11
+ }
12
+ return nodes;
13
+ }
14
+
15
+ function getNodesHeight(nodes) {
16
+ // include borders + padding; exclude margins
17
+ return nodes.reduce((acc, n) => acc + n.getBoundingClientRect().height, 0);
18
+ }
19
+
20
+ /**
21
+ * Returns a live pixel offset equal to the total height of the element(s)
22
+ * matched by `selector`. Updates on resize, content changes, and CSS class
23
+ * toggles (useful for Collapse animations).
24
+ *
25
+ * Usage:
26
+ * useStickyOffset('.header')
27
+ * useStickyOffset({ selector: ['.topbar', '.filters'], extra: 8 })
28
+ */
29
+ export default function useStickyOffset(selectorOrOptions, extra = 0) {
30
+ const isObj =
31
+ selectorOrOptions &&
32
+ typeof selectorOrOptions === 'object' &&
33
+ !Array.isArray(selectorOrOptions);
34
+
35
+ const selector = isObj
36
+ ? (selectorOrOptions.selector ?? selectorOrOptions.belowSelector ?? null)
37
+ : selectorOrOptions;
38
+
39
+ // allow both `{ extra }` in options and the 2nd positional arg
40
+ const extraAdd =
41
+ (isObj ? Number(selectorOrOptions.extra || 0) : Number(extra || 0)) || 0;
42
+
43
+ const [offset, setOffset] = useState(0);
44
+ const rafRef = useRef(null);
45
+ const nodesRef = useRef([]);
46
+
47
+ const update = () => {
48
+ const nodes = nodesRef.current;
49
+ const h = nodes.length ? getNodesHeight(nodes) : 0;
50
+ // 1px threshold avoids micro-flutters during transitions
51
+ setOffset((prev) => {
52
+ const next = h + extraAdd;
53
+ return Math.abs(prev - next) >= 1 ? next : prev;
54
+ });
55
+ };
56
+
57
+ // use layout effect on client so first paint has correct offset
58
+ const useLE = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
59
+
60
+ useLE(() => {
61
+ if (!selector) {
62
+ setOffset(extraAdd);
63
+ return;
64
+ }
65
+ nodesRef.current = resolveNodes(selector);
66
+
67
+ // initial measure
68
+ update();
69
+
70
+ // observe size changes
71
+ const ros = [];
72
+ if (typeof window !== 'undefined' && 'ResizeObserver' in window) {
73
+ for (const n of nodesRef.current) {
74
+ const ro = new ResizeObserver(() => update());
75
+ ro.observe(n);
76
+ ros.push(ro);
77
+ }
78
+ }
79
+
80
+ // observe attribute/class changes that may affect height
81
+ const mos = [];
82
+ if (typeof window !== 'undefined' && 'MutationObserver' in window) {
83
+ for (const n of nodesRef.current) {
84
+ const mo = new MutationObserver(() => {
85
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
86
+ rafRef.current = requestAnimationFrame(update);
87
+ });
88
+ mo.observe(n, {
89
+ attributes: true,
90
+ attributeFilter: ['style', 'class', 'data-open', 'aria-expanded'],
91
+ childList: true,
92
+ subtree: true
93
+ });
94
+ mos.push(mo);
95
+ }
96
+ }
97
+
98
+ const onWin = () => update();
99
+ if (typeof window !== 'undefined') {
100
+ window.addEventListener('resize', onWin);
101
+ }
102
+
103
+ return () => {
104
+ if (typeof window !== 'undefined') {
105
+ window.removeEventListener('resize', onWin);
106
+ }
107
+ ros.forEach((ro) => ro.disconnect());
108
+ mos.forEach((mo) => mo.disconnect());
109
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
110
+ };
111
+ // stringify option to capture selector arrays etc.
112
+ }, [JSON.stringify(selector), extraAdd]);
113
+
114
+ return offset;
115
+ }
@@ -0,0 +1,81 @@
1
+ if True:
2
+ import sys
3
+
4
+ sys.path.append("../../../")
5
+ import dash
6
+ from dash import html
7
+
8
+ import feffery_antd_components as fac
9
+
10
+ app = dash.Dash(__name__)
11
+
12
+ app.layout = html.Div(
13
+ [
14
+ # ---- sticky top section whose height can change (collapse) ----
15
+ html.Div(
16
+ fac.AntdAccordion(
17
+ items=[
18
+ {
19
+ "title": f"手风琴项示例{i}",
20
+ "key": i,
21
+ "children": fac.AntdText(f"手风琴项示例{i}"),
22
+ }
23
+ for i in range(1, 6)
24
+ ]
25
+ ),
26
+ # this outer wrapper is what the hook observes
27
+ className="filters-shell",
28
+ style={
29
+ "position": "sticky",
30
+ "top": 0,
31
+ "zIndex": 20,
32
+ "background": "white",
33
+ "borderBottom": "1px solid #f0f0f0",
34
+ },
35
+ ),
36
+ html.Br(),
37
+ fac.AntdTable(
38
+ columns=[
39
+ {"title": f"Field {i}", "dataIndex": f"Field {i}"} for i in range(5)
40
+ ],
41
+ data=[
42
+ {**{f"Field {j}": i for j in range(5)}, "key": f"row-{i}"}
43
+ for i in range(60)
44
+ ],
45
+ bordered=True,
46
+ size="middle",
47
+ className="sticky-table",
48
+ sticky={"belowSelector": ".filters-shell"},
49
+ pagination={"pageSize": 100, "position": "bottomCenter"},
50
+ expandedRowKeyToContent=[
51
+ {
52
+ "key": f"row-{i}",
53
+ "content": fac.AntdTable(
54
+ columns=[
55
+ {"title": f"Nested {i}", "dataIndex": f"Field {i}"}
56
+ for i in range(5)
57
+ ],
58
+ data=[
59
+ {**{f"Field {j}": i for j in range(5)}, "key": f"row-{i}"}
60
+ for i in range(60)
61
+ ],
62
+ bordered=True,
63
+ size="middle",
64
+ sticky={
65
+ "belowSelector": [
66
+ ".filters-shell",
67
+ ".sticky-table .ant-table-header.ant-table-sticky-holder",
68
+ ],
69
+ },
70
+ pagination={"pageSize": 100, "position": "bottomCenter"},
71
+ ),
72
+ }
73
+ for i in range(60)
74
+ ],
75
+ ),
76
+ ],
77
+ style={"padding": 24, "maxWidth": 1000, "margin": "0 auto"},
78
+ )
79
+
80
+ if __name__ == "__main__":
81
+ app.run(debug=True)
@@ -0,0 +1,84 @@
1
+ if True:
2
+ import sys
3
+
4
+ sys.path.append('../../../')
5
+ import dash
6
+ from dash import html
7
+ from datetime import datetime
8
+ import feffery_antd_components as fac
9
+
10
+ app = dash.Dash(__name__)
11
+
12
+ app.layout = html.Div(
13
+ [
14
+ fac.AntdTable(
15
+ columns=[
16
+ {
17
+ 'title': 'select示例1',
18
+ 'dataIndex': 'select示例1',
19
+ 'renderOptions': {
20
+ 'renderType': 'select'
21
+ },
22
+ },
23
+ {
24
+ 'title': 'placeholder示例',
25
+ 'dataIndex': 'placeholder示例',
26
+ 'editable': True,
27
+ 'width': '20%',
28
+ 'editOptions': {
29
+ 'placeholder': '请输入内容'
30
+ },
31
+ },
32
+ {
33
+ 'title': 'int型示例',
34
+ 'dataIndex': 'int型示例',
35
+ 'editable': True,
36
+ },
37
+ {
38
+ 'title': 'float型示例',
39
+ 'dataIndex': 'float型示例',
40
+ 'editable': True,
41
+ },
42
+ {
43
+ 'title': 'str型示例',
44
+ 'dataIndex': 'str型示例',
45
+ 'editable': True,
46
+ },
47
+ {
48
+ 'title': '日期时间示例',
49
+ 'dataIndex': '日期时间示例',
50
+ 'editable': True,
51
+ },
52
+ ],
53
+ data=[
54
+ {
55
+ 'select示例1': {
56
+ 'options': [
57
+ {
58
+ 'label': f'选项{j}',
59
+ 'value': f'选项{j}',
60
+ }
61
+ for j in range(5)
62
+ ],
63
+ 'allowClear': True,
64
+ 'placeholder': '请选择',
65
+ },
66
+ 'int型示例': 123,
67
+ 'float型示例': 1.23,
68
+ 'str型示例': '示例字符',
69
+ '日期时间示例': datetime.now().strftime(
70
+ '%Y-%m-%d %H:%M:%S'
71
+ ),
72
+ }
73
+ ]
74
+ * 3,
75
+ bordered=True,
76
+ tableLayout='fixed',
77
+ )
78
+ ],
79
+ style={'padding': 100},
80
+ )
81
+
82
+
83
+ if __name__ == '__main__':
84
+ app.run(debug=True)