@ticatec/topside-elements 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.3
4
+
5
+ ### 变更
6
+ - **Split 分割线外观**:线条默认 1px(`width`,原 2px);新增 `hitSize`(不占布局的感应区,默认 8px,靠近即可悬停 / 拖动)、`color`、`gripColor`、`gripSize`、`gripLength`;悬停 / 拖动 / 键盘聚焦时中间淡入深色稍粗的把手,取代原来整条加深。新令牌 `--topside-divider-grip-color`(亮 `#94A3B8` / 暗 `#8a8a8a`)。
7
+
8
+ ### 文档
9
+ - 重新生成 `docs/components`(Split、DataTable / ConciseDataTable 合计行相关属性)。
10
+
3
11
  ## 1.0.0 — 首个公开版本
4
12
 
5
13
  ### 新增
@@ -3,18 +3,20 @@
3
3
  A draggable divider that resizes the panel you bind it to. Sits between two adjacent panels in a flex container; dragging the divider updates the bound panel's `width` or `height` inline style.
4
4
 
5
5
  > **Naming gotcha**: `direction` describes the **visual orientation of the divider bar**, not the layout direction of the panels.
6
- > - `direction="vertical"` → a *vertical bar* (1px wide, full height) → goes between **left/right** panels (flex row).
7
- > - `direction="horizontal"` → a *horizontal bar* (full width, 1px tall) → goes between **top/bottom** panels (flex column).
6
+ > - `direction="vertical"` → a *vertical bar* (`width` px wide, full height) → goes between **left/right** panels (flex row).
7
+ > - `direction="horizontal"` → a *horizontal bar* (full width, `width` px tall) → goes between **top/bottom** panels (flex column).
8
8
 
9
9
  ## Features
10
10
 
11
11
  - **Drag-to-resize**: mouse-driven resizing of the bound panel
12
12
  - **Two orientations**: vertical bar (default) for left/right splits, horizontal bar for top/bottom splits
13
- - **Adjustable thickness**: `width` prop controls the bar's px thickness
13
+ - **Thin line, generous hit area**: the visible line is 1px by default (`width`), while an invisible `hitSize` zone (8px) around it catches hover and drag without taking layout space
14
+ - **Hover handle**: a darker, thicker, rounded segment fades in at the center on hover / drag / keyboard focus (like the sidebar resizer in Claude)
15
+ - **Customisable look**: `color`, `gripColor`, `gripSize`, `gripLength`
14
16
  - **Reverse mode**: for when the bound panel is on the right/bottom of the divider
15
17
  - **Fixed mode**: omit `bindingPanel` to render a non-interactive visual divider
16
18
  - **Min size guard**: bound panel can't be dragged below 5px
17
- - **Theme token**: color via `--topside-dividing-line-color`
19
+ - **Theme tokens**: `--topside-dividing-line-color`, `--topside-divider-grip-color`
18
20
  - **Cleanup on unmount**: in-flight drag is cancelled if the component is destroyed mid-drag
19
21
 
20
22
  ## Installation
@@ -57,7 +59,12 @@ Drag the bar; the left panel's width updates in real time. The right panel (with
57
59
  | Prop | Type | Default | Description |
58
60
  |------|------|---------|-------------|
59
61
  | `direction` | `'vertical' \| 'horizontal'` | `'vertical'` | Visual orientation of the bar. `vertical` = thin tall bar (for left/right splits); `horizontal` = thin wide bar (for top/bottom splits). |
60
- | `width` | `number` | `2` | Thickness of the bar in pixels |
62
+ | `width` | `number` | `1` | Thickness of the visible line in px (takes layout space) |
63
+ | `hitSize` | `number` | `8` | Thickness of the invisible hover/drag zone in px, centered on the line; takes no layout space |
64
+ | `color` | `string` | token | Line color; overrides `--topside-dividing-line-color` |
65
+ | `gripColor` | `string` | token | Color of the handle shown on hover/drag; overrides `--topside-divider-grip-color` |
66
+ | `gripSize` | `number` | `3` | Handle thickness in px |
67
+ | `gripLength` | `number` | `32` | Handle length in px; `0` = full length |
61
68
  | `bindingPanel` | `HTMLElement \| undefined` | `undefined` | The panel to resize. Omit to render a fixed, non-draggable divider. |
62
69
  | `reverse` | `boolean` | `false` | Flip drag direction. Use when the bound panel sits to the **right/below** the divider (so dragging away from it grows it). |
63
70
  | `onResizeEnd` | `(size: number) => void` | `undefined` | Fired once when a drag ends; receives the bound panel's final `clientWidth` or `clientHeight` in px. Not fired for fixed dividers. |
@@ -94,7 +101,7 @@ The component does **not** emit intermediate events during the drag — it mutat
94
101
  <p>Drag the bar to resize me.</p>
95
102
  </div>
96
103
 
97
- <Split bindingPanel={leftPanel} direction="vertical" width={4} />
104
+ <Split bindingPanel={leftPanel} direction="vertical" />
98
105
 
99
106
  <div style="flex: 1 1 auto; padding: 16px; background: #e8e8e8;">
100
107
  <h3>Right</h3>
@@ -117,7 +124,7 @@ The component does **not** emit intermediate events during the drag — it mutat
117
124
  <h3>Top</h3>
118
125
  </div>
119
126
 
120
- <Split bindingPanel={topPanel} direction="horizontal" width={4} />
127
+ <Split bindingPanel={topPanel} direction="horizontal" />
121
128
 
122
129
  <div style="flex: 1 1 auto; padding: 16px; background: #e8e8e8;">
123
130
  <h3>Bottom</h3>
@@ -186,13 +193,13 @@ Sidebar | Editor | Details, all resizable. Two dividers, both bound to the panel
186
193
  Sidebar
187
194
  </div>
188
195
 
189
- <Split bindingPanel={sidebar} direction="vertical" width={4} />
196
+ <Split bindingPanel={sidebar} direction="vertical" />
190
197
 
191
198
  <div bind:this={editor} style="flex: 1 1 0; background: #0f172a; color: #e2e8f0;">
192
199
  Editor
193
200
  </div>
194
201
 
195
- <Split bindingPanel={editor} direction="vertical" width={4} />
202
+ <Split bindingPanel={editor} direction="vertical" />
196
203
 
197
204
  <div style="flex: 0 0 320px; background: #111827; color: #e2e8f0;">
198
205
  Details
@@ -214,76 +221,54 @@ Omit `bindingPanel`:
214
221
  </div>
215
222
  ```
216
223
 
217
- Fixed dividers get the `fixed` class → `pointer-events: none; cursor: default`. The hover/active states are also suppressed.
224
+ Fixed dividers get the `fixed` class → `pointer-events: none; cursor: default`; the hit area and hover handle are not rendered.
218
225
 
219
- ## DOM Structure & CSS
226
+ ## Styling Examples
227
+
228
+ ```svelte
229
+ <!-- Claude-style: 1px line, wide hit area, short indigo handle -->
230
+ <Split bindingPanel={sidebar} width={1} hitSize={12} gripSize={4} gripLength={48}
231
+ color="#e5e7eb" gripColor="#6366f1" />
220
232
 
221
- Rendered output:
233
+ <!-- Whole line thickens on hover -->
234
+ <Split bindingPanel={sidebar} gripLength={0} />
235
+ ```
236
+
237
+ ## DOM Structure & CSS
222
238
 
223
239
  ```html
224
240
  <div
225
- class="topside-divider {direction}" <!-- + 'fixed' if no bindingPanel -->
226
- class:fixed
227
- style="height: 2px" or "width: 2px" <!-- depends on direction -->
241
+ class="topside-divider {direction}" <!-- + 'fixed' without bindingPanel, + 'dragging' while dragging -->
242
+ style="width: 1px; --split-hit: 8px; --split-grip-size: 3px; --split-grip-length: 32px; ..."
228
243
  role="separator"
229
244
  aria-orientation="{direction}"
230
245
  aria-label="Resize handle"
231
246
  tabindex="0"
232
- onmousedown={startDrag}
233
- />
247
+ ></div>
234
248
  ```
235
249
 
236
- Built-in styles:
237
-
238
- ```css
239
- .topside-divider {
240
- background-color: var(--topside-dividing-line-color, #CBD5E1);
241
- flex: 0 0 auto; /* don't grow or shrink */
242
- transition: background-color 0.2s ease;
243
- }
244
-
245
- .topside-divider.vertical {
246
- height: 100%;
247
- cursor: col-resize;
248
- }
249
-
250
- .topside-divider.horizontal {
251
- width: 100%;
252
- cursor: row-resize;
253
- }
254
-
255
- .topside-divider.fixed {
256
- cursor: default;
257
- pointer-events: none;
258
- }
259
-
260
- .topside-divider:hover {
261
- /* darken 20% via color-mix */
262
- background-color: color-mix(in srgb, var(--topside-dividing-line-color, #CBD5E1) 80%, #000);
263
- }
264
-
265
- .topside-divider:active {
266
- transition: none; /* no animation lag during drag */
267
- }
268
- ```
250
+ - The element itself is the visible line (`width` px thick, `flex: 0 0 auto`), `position: relative; z-index: 5`.
251
+ - `::before` is the transparent **hit area**: centered on the line, `hitSize` px thick. It overlaps the neighbouring panels by about `(hitSize - width) / 2` px on each side, so hovering near the line already shows the resize cursor.
252
+ - `::after` is the **handle**: centered, `gripSize` × `gripLength`, rounded, `opacity: 0`. It fades in on `:hover` (100ms delay, so passing over it doesn't flicker), and immediately while `.dragging` or `:focus-visible`.
253
+ - Fixed dividers hide both pseudo-elements and ignore pointer events.
269
254
 
270
255
  ### CSS Custom Properties
271
256
 
272
- | Variable | Default | Description |
257
+ | Variable | Default (light / dark) | Description |
273
258
  |----------|---------|-------------|
274
- | `--topside-dividing-line-color` | `#CBD5E1` | Background color of the bar (hover state darkens it 20%) |
259
+ | `--topside-dividing-line-color` | `#CBD5E1` / `#4a4a4a` | Line color (overridden by the `color` prop) |
260
+ | `--topside-divider-grip-color` | `#94A3B8` / `#8a8a8a` | Handle color (overridden by the `gripColor` prop) |
275
261
 
276
262
  ```css
277
263
  :root {
278
- --topside-dividing-line-color: #d4d4d8;
279
- }
280
- .dark-theme {
281
- --topside-dividing-line-color: #3f3f46;
264
+ --topside-dividing-line-color: #e5e7eb;
265
+ --topside-divider-grip-color: #9ca3af;
282
266
  }
283
267
  ```
284
268
 
285
269
  ## Behavior Notes
286
270
 
271
+ - **Hit area overlaps neighbours.** Clicks within about `hitSize / 2` px of the line go to the divider. If a panel has a button or scrollbar right at that edge, lower `hitSize`.
287
272
  - **Mouse only.** The drag uses `mousedown` / `mousemove` / `mouseup`. Touch events are not handled — on touch devices the divider will not respond to finger drags.
288
273
  - **Min size**: bound panel clamped to ≥5px (won't collapse to zero).
289
274
  - **Document-level listeners**: `mousemove` and `mouseup` are attached to `document` during a drag, so the cursor can wander outside the bar without losing the grab.
@@ -295,7 +280,7 @@ Built-in styles:
295
280
  ## Accessibility
296
281
 
297
282
  - `role="separator"`, `aria-orientation`, `aria-label="Resize handle"`, `tabindex="0"` are set on the divider.
298
- - **Keyboard support is partial**: the divider is focusable but the component does **not** handle arrow-key events. Dragging requires a mouse. If you need keyboard resizing, layer your own `keydown` handler on a wrapping element.
283
+ - **Keyboard**: when focused, arrow keys (←/→ for vertical, ↑/↓ for horizontal) resize the bound panel by 10px and fire `onResizeEnd`; the handle is shown while focused via keyboard.
299
284
  - For fixed dividers, consider `aria-disabled="true"` if the visual still looks interactive.
300
285
 
301
286
  ## Best Practices
@@ -3,18 +3,20 @@
3
3
  一个可拖动的分隔条,用来调整绑定的那个面板的尺寸。放在 flex 容器里两个相邻面板之间,拖动会直接改写绑定面板的 `width` 或 `height` 内联样式。
4
4
 
5
5
  > **命名陷阱**:`direction` 描述的是**分隔条本身的视觉朝向**,不是面板的排布方向。
6
- > - `direction="vertical"` → 一根**竖条**(1px 宽,整高)→ 放在**左右**面板之间(flex row)。
7
- > - `direction="horizontal"` → 一根**横条**(整宽,1px 高)→ 放在**上下**面板之间(flex column)。
6
+ > - `direction="vertical"` → 一根**竖条**(`width` px 宽,整高)→ 放在**左右**面板之间(flex row)。
7
+ > - `direction="horizontal"` → 一根**横条**(整宽,`width` px 高)→ 放在**上下**面板之间(flex column)。
8
8
 
9
9
  ## 功能特性
10
10
 
11
11
  - **拖拽调整**:鼠标拖动改变绑定面板尺寸
12
12
  - **两个方向**:竖条(默认,用于左右分栏)或横条(用于上下分栏)
13
- - **可调粗细**:`width` 属性控制条的像素厚度
13
+ - **细线 + 宽感应区**:可见线条默认 1px(`width`),周围有一圈不占布局的透明感应区(`hitSize`,默认 8px),靠近线条即可悬停、拖动
14
+ - **悬停把手**:悬停 / 拖动 / 键盘聚焦时,线条中间淡入一段深色、稍粗、圆角的把手(类似 Claude 侧边栏)
15
+ - **外观可调**:`color`、`gripColor`、`gripSize`、`gripLength`
14
16
  - **反向模式**:当绑定面板在分隔条的右侧/下方时使用
15
17
  - **固定模式**:省略 `bindingPanel` 即渲染为不可交互的视觉分隔条
16
18
  - **最小尺寸保护**:绑定面板不会被拖到 5px 以下
17
- - **主题令牌**:颜色由 `--topside-dividing-line-color` 驱动
19
+ - **主题令牌**:`--topside-dividing-line-color`、`--topside-divider-grip-color`
18
20
  - **销毁时清理**:组件在拖动过程中被销毁会自动取消拖动
19
21
 
20
22
  ## 安装
@@ -57,7 +59,12 @@ import Split from '@ticatec/topside-elements/Split';
57
59
  | 属性 | 类型 | 默认值 | 描述 |
58
60
  |------|------|---------|-------------|
59
61
  | `direction` | `'vertical' \| 'horizontal'` | `'vertical'` | 分隔条的视觉朝向。`vertical` = 细高竖条(用于左右分栏);`horizontal` = 细宽横条(用于上下分栏) |
60
- | `width` | `number` | `2` | 分隔条的像素厚度 |
62
+ | `width` | `number` | `1` | 可见线条的像素厚度(占布局) |
63
+ | `hitSize` | `number` | `8` | 鼠标感应区厚度(px),以线条为中心向两侧伸出,不占布局 |
64
+ | `color` | `string` | 令牌 | 线条底色,覆盖 `--topside-dividing-line-color` |
65
+ | `gripColor` | `string` | 令牌 | 悬停 / 拖动时把手颜色,覆盖 `--topside-divider-grip-color` |
66
+ | `gripSize` | `number` | `3` | 把手粗细(px) |
67
+ | `gripLength` | `number` | `32` | 把手长度(px),`0` 表示整条 |
61
68
  | `bindingPanel` | `HTMLElement \| undefined` | `undefined` | 要调整尺寸的面板。省略则渲染为固定不可拖的分隔条 |
62
69
  | `reverse` | `boolean` | `false` | 反转拖动方向。绑定面板在分隔条**右侧/下方**时使用(向远离面板的方向拖动使其变大) |
63
70
  | `onResizeEnd` | `(size: number) => void` | `undefined` | 一次拖动结束时触发一次,参数是绑定面板最终的 `clientWidth` 或 `clientHeight`(像素)。固定分隔条不会触发 |
@@ -94,7 +101,7 @@ import Split from '@ticatec/topside-elements/Split';
94
101
  <p>拖动中间的条调整我的宽度。</p>
95
102
  </div>
96
103
 
97
- <Split bindingPanel={leftPanel} direction="vertical" width={4} />
104
+ <Split bindingPanel={leftPanel} direction="vertical" />
98
105
 
99
106
  <div style="flex: 1 1 auto; padding: 16px; background: #e8e8e8;">
100
107
  <h3>右侧</h3>
@@ -117,7 +124,7 @@ import Split from '@ticatec/topside-elements/Split';
117
124
  <h3>顶部</h3>
118
125
  </div>
119
126
 
120
- <Split bindingPanel={topPanel} direction="horizontal" width={4} />
127
+ <Split bindingPanel={topPanel} direction="horizontal" />
121
128
 
122
129
  <div style="flex: 1 1 auto; padding: 16px; background: #e8e8e8;">
123
130
  <h3>底部</h3>
@@ -186,13 +193,13 @@ import Split from '@ticatec/topside-elements/Split';
186
193
  侧边栏
187
194
  </div>
188
195
 
189
- <Split bindingPanel={sidebar} direction="vertical" width={4} />
196
+ <Split bindingPanel={sidebar} direction="vertical" />
190
197
 
191
198
  <div bind:this={editor} style="flex: 1 1 0; background: #0f172a; color: #e2e8f0;">
192
199
  编辑器
193
200
  </div>
194
201
 
195
- <Split bindingPanel={editor} direction="vertical" width={4} />
202
+ <Split bindingPanel={editor} direction="vertical" />
196
203
 
197
204
  <div style="flex: 0 0 320px; background: #111827; color: #e2e8f0;">
198
205
  详情
@@ -214,76 +221,54 @@ import Split from '@ticatec/topside-elements/Split';
214
221
  </div>
215
222
  ```
216
223
 
217
- 固定分隔条会加 `fixed` 类 → `pointer-events: none; cursor: default`,hover/active 状态也会失效。
224
+ 固定分隔条会加 `fixed` 类 → `pointer-events: none; cursor: default`,不渲染感应区和把手。
218
225
 
219
- ## DOM 结构与 CSS
226
+ ## 样式示例
227
+
228
+ ```svelte
229
+ <!-- Claude 风格:1px 细线、12px 感应区、短的靛蓝把手 -->
230
+ <Split bindingPanel={sidebar} width={1} hitSize={12} gripSize={4} gripLength={48}
231
+ color="#e5e7eb" gripColor="#6366f1" />
220
232
 
221
- 实际渲染:
233
+ <!-- 悬停时整条加粗 -->
234
+ <Split bindingPanel={sidebar} gripLength={0} />
235
+ ```
236
+
237
+ ## DOM 结构与 CSS
222
238
 
223
239
  ```html
224
240
  <div
225
- class="topside-divider {direction}" <!-- 无 bindingPanel 时再加 'fixed' -->
226
- class:fixed
227
- style="height: 2px" 或 "width: 2px" <!-- 取决于 direction -->
241
+ class="topside-divider {direction}" <!-- 无 bindingPanel 时加 'fixed',拖动中加 'dragging' -->
242
+ style="width: 1px; --split-hit: 8px; --split-grip-size: 3px; --split-grip-length: 32px; ..."
228
243
  role="separator"
229
244
  aria-orientation="{direction}"
230
245
  aria-label="Resize handle"
231
246
  tabindex="0"
232
- onmousedown={startDrag}
233
- />
247
+ ></div>
234
248
  ```
235
249
 
236
- 内置样式:
237
-
238
- ```css
239
- .topside-divider {
240
- background-color: var(--topside-dividing-line-color, #CBD5E1);
241
- flex: 0 0 auto; /* 不膨胀不收缩 */
242
- transition: background-color 0.2s ease;
243
- }
244
-
245
- .topside-divider.vertical {
246
- height: 100%;
247
- cursor: col-resize;
248
- }
249
-
250
- .topside-divider.horizontal {
251
- width: 100%;
252
- cursor: row-resize;
253
- }
254
-
255
- .topside-divider.fixed {
256
- cursor: default;
257
- pointer-events: none;
258
- }
259
-
260
- .topside-divider:hover {
261
- /* color-mix 加深 20% */
262
- background-color: color-mix(in srgb, var(--topside-dividing-line-color, #CBD5E1) 80%, #000);
263
- }
264
-
265
- .topside-divider:active {
266
- transition: none; /* 拖动时无动画延迟 */
267
- }
268
- ```
250
+ - 元素本身就是可见线条(粗 `width` px,`flex: 0 0 auto`),`position: relative; z-index: 5`。
251
+ - `::before` 是透明**感应区**:以线条为中心、厚 `hitSize` px,向两侧各盖住相邻面板约 `(hitSize - width) / 2` px,鼠标靠近线条就出现拖动光标。
252
+ - `::after` 是**把手**:居中、`gripSize` × `gripLength`、圆角,平时 `opacity: 0`;`:hover` 时延迟 100ms 淡入(划过不闪),`.dragging` 或 `:focus-visible` 时立即显示。
253
+ - 固定分隔条隐藏两个伪元素,不响应鼠标。
269
254
 
270
255
  ### CSS 自定义属性
271
256
 
272
- | 变量 | 默认值 | 描述 |
257
+ | 变量 | 默认值(亮 / 暗) | 描述 |
273
258
  |------|--------|------|
274
- | `--topside-dividing-line-color` | `#CBD5E1` | 分隔条背景色(hover 时自动加深 20%) |
259
+ | `--topside-dividing-line-color` | `#CBD5E1` / `#4a4a4a` | 线条底色(`color` 属性优先) |
260
+ | `--topside-divider-grip-color` | `#94A3B8` / `#8a8a8a` | 把手颜色(`gripColor` 属性优先) |
275
261
 
276
262
  ```css
277
263
  :root {
278
- --topside-dividing-line-color: #d4d4d8;
279
- }
280
- .dark-theme {
281
- --topside-dividing-line-color: #3f3f46;
264
+ --topside-dividing-line-color: #e5e7eb;
265
+ --topside-divider-grip-color: #9ca3af;
282
266
  }
283
267
  ```
284
268
 
285
269
  ## 行为说明
286
270
 
271
+ - **感应区会盖住相邻面板边缘。** 距线条约 `hitSize / 2` px 内的点击由分隔条接收;面板边上正好有按钮或滚动条时,调小 `hitSize`。
287
272
  - **仅鼠标。** 拖动用的是 `mousedown` / `mousemove` / `mouseup`。**不**处理 touch 事件,触屏设备上手指拖动不会响应。
288
273
  - **最小尺寸**:绑定面板夹到 ≥5px(不会塌成 0)。
289
274
  - **document 级监听**:拖动期间 `mousemove` 和 `mouseup` 挂在 `document` 上,光标移出分隔条也不会丢失拖动。
@@ -295,7 +280,7 @@ import Split from '@ticatec/topside-elements/Split';
295
280
  ## 无障碍访问
296
281
 
297
282
  - 分隔条上设置了 `role="separator"`、`aria-orientation`、`aria-label="Resize handle"`、`tabindex="0"`。
298
- - **键盘支持有限**:可聚焦,但组件**不**处理方向键。拖动必须用鼠标。需要键盘调整的话,自己在父元素上叠一层 `keydown`。
283
+ - **键盘**:聚焦后方向键(竖线 ←/→,横线 ↑/↓)每次调整绑定面板 10px 并触发 `onResizeEnd`;键盘聚焦时显示把手。
299
284
  - 固定分隔条如果视觉上还像可交互,建议加 `aria-disabled="true"`。
300
285
 
301
286
  ## 最佳实践
@@ -2,18 +2,38 @@
2
2
  import {onDestroy} from 'svelte';
3
3
 
4
4
  interface Props {
5
+ /** 分隔线朝向:vertical 竖线(左右分栏),horizontal 横线(上下分栏) */
5
6
  direction?: "horizontal" | "vertical";
7
+ /** 可见线条粗细(px),占布局,默认 1 */
6
8
  width?: number;
9
+ /** 被拖动调整尺寸的面板;不传则为固定分隔线 */
7
10
  bindingPanel?: HTMLElement;
11
+ /** 绑定面板在分隔线右侧 / 下方时设为 true */
8
12
  reverse?: boolean;
13
+ /** 线条底色,默认取 --topside-dividing-line-color */
14
+ color?: string;
15
+ /** 悬停 / 拖动时中间把手的颜色,默认取 --topside-divider-grip-color */
16
+ gripColor?: string;
17
+ /** 把手粗细(px),默认 3 */
18
+ gripSize?: number;
19
+ /** 把手长度(px),默认 32;传 0 表示整条加粗 */
20
+ gripLength?: number;
21
+ /** 鼠标感应区厚度(px),不占布局,默认 8 */
22
+ hitSize?: number;
23
+ /** 拖动结束时回调,参数为绑定面板最终宽 / 高(px) */
9
24
  onResizeEnd?: (size: number) => void;
10
25
  }
11
26
 
12
27
  let {
13
28
  direction = "vertical",
14
- width = 2,
29
+ width = 1,
15
30
  bindingPanel,
16
31
  reverse = false,
32
+ color,
33
+ gripColor,
34
+ gripSize = 3,
35
+ gripLength = 32,
36
+ hitSize = 8,
17
37
  onResizeEnd
18
38
  }: Props = $props();
19
39
 
@@ -21,7 +41,14 @@
21
41
  let startPos = $state(0);
22
42
  let startSize = $state(0);
23
43
 
24
- const style = $derived(direction === "horizontal" ? `height: ${width}px` : `width: ${width}px`);
44
+ const style = $derived([
45
+ direction === "horizontal" ? `height: ${width}px` : `width: ${width}px`,
46
+ `--split-hit: ${Math.max(hitSize, width)}px`,
47
+ `--split-grip-size: ${gripSize}px`,
48
+ gripLength > 0 ? `--split-grip-length: ${gripLength}px` : `--split-grip-length: 100%`,
49
+ color ? `--split-color: ${color}` : "",
50
+ gripColor ? `--split-grip-color: ${gripColor}` : ""
51
+ ].filter(Boolean).join("; "));
25
52
  const fixed = $derived(!bindingPanel);
26
53
 
27
54
  const calculateNewSize = (currentPos: number): number => {
@@ -107,6 +134,7 @@
107
134
  <div
108
135
  class="topside-divider {direction}"
109
136
  class:fixed
137
+ class:dragging={isDragging}
110
138
  {style}
111
139
  role="separator"
112
140
  aria-orientation={direction}
@@ -118,9 +146,18 @@
118
146
 
119
147
  <style>
120
148
  .topside-divider {
121
- background-color: var(--topside-dividing-line-color, #CBD5E1);
149
+ --_color: var(--split-color, var(--topside-dividing-line-color, #CBD5E1));
150
+ --_grip: var(--split-grip-color, var(--topside-divider-grip-color, #94A3B8));
151
+ position: relative;
152
+ z-index: 5;
122
153
  flex: 0 0 auto;
123
- transition: background-color 0.2s ease;
154
+ background-color: var(--_color);
155
+ outline: none;
156
+ }
157
+
158
+ .topside-divider.vertical {
159
+ height: 100%;
160
+ cursor: col-resize;
124
161
  }
125
162
 
126
163
  .topside-divider.horizontal {
@@ -128,23 +165,71 @@
128
165
  cursor: row-resize;
129
166
  }
130
167
 
131
- .topside-divider.vertical {
132
- height: 100%;
133
- cursor: col-resize;
168
+ /* 感应区:透明、不占布局,向两侧伸出,靠近线条即可触发 hover */
169
+ .topside-divider::before {
170
+ content: "";
171
+ position: absolute;
134
172
  }
135
173
 
136
- .topside-divider.fixed {
137
- cursor: default;
174
+ .topside-divider.vertical::before {
175
+ top: 0;
176
+ bottom: 0;
177
+ left: 50%;
178
+ width: var(--split-hit, 8px);
179
+ transform: translateX(-50%);
180
+ }
181
+
182
+ .topside-divider.horizontal::before {
183
+ left: 0;
184
+ right: 0;
185
+ top: 50%;
186
+ height: var(--split-hit, 8px);
187
+ transform: translateY(-50%);
188
+ }
189
+
190
+ /* 把手:居中的深色稍粗线段,平时隐藏 */
191
+ .topside-divider::after {
192
+ content: "";
193
+ position: absolute;
194
+ left: 50%;
195
+ top: 50%;
196
+ transform: translate(-50%, -50%);
197
+ border-radius: 999px;
198
+ background-color: var(--_grip);
199
+ opacity: 0;
138
200
  pointer-events: none;
201
+ transition: opacity 0.15s ease;
202
+ }
203
+
204
+ .topside-divider.vertical::after {
205
+ width: var(--split-grip-size, 3px);
206
+ height: var(--split-grip-length, 32px);
207
+ }
208
+
209
+ .topside-divider.horizontal::after {
210
+ height: var(--split-grip-size, 3px);
211
+ width: var(--split-grip-length, 32px);
212
+ }
213
+
214
+ /* 稍作延迟,鼠标划过时不闪 */
215
+ .topside-divider:hover::after {
216
+ opacity: 1;
217
+ transition-delay: 0.1s;
218
+ }
219
+
220
+ .topside-divider.dragging::after,
221
+ .topside-divider:focus-visible::after {
222
+ opacity: 1;
223
+ transition-delay: 0s;
139
224
  }
140
225
 
141
- /* 拖动时移除过渡效果,提高响应性 */
142
- .topside-divider:hover {
143
- background-color: color-mix(in srgb, var(--topside-dividing-line-color, #CBD5E1) 80%, #000);
226
+ .topside-divider.fixed {
227
+ cursor: default;
228
+ pointer-events: none;
144
229
  }
145
230
 
146
- /* 拖动时移除过渡效果,提高响应性 */
147
- .topside-divider:active {
148
- transition: none;
231
+ .topside-divider.fixed::before,
232
+ .topside-divider.fixed::after {
233
+ display: none;
149
234
  }
150
235
  </style>
@@ -1,8 +1,23 @@
1
1
  interface Props {
2
+ /** 分隔线朝向:vertical 竖线(左右分栏),horizontal 横线(上下分栏) */
2
3
  direction?: "horizontal" | "vertical";
4
+ /** 可见线条粗细(px),占布局,默认 1 */
3
5
  width?: number;
6
+ /** 被拖动调整尺寸的面板;不传则为固定分隔线 */
4
7
  bindingPanel?: HTMLElement;
8
+ /** 绑定面板在分隔线右侧 / 下方时设为 true */
5
9
  reverse?: boolean;
10
+ /** 线条底色,默认取 --topside-dividing-line-color */
11
+ color?: string;
12
+ /** 悬停 / 拖动时中间把手的颜色,默认取 --topside-divider-grip-color */
13
+ gripColor?: string;
14
+ /** 把手粗细(px),默认 3 */
15
+ gripSize?: number;
16
+ /** 把手长度(px),默认 32;传 0 表示整条加粗 */
17
+ gripLength?: number;
18
+ /** 鼠标感应区厚度(px),不占布局,默认 8 */
19
+ hitSize?: number;
20
+ /** 拖动结束时回调,参数为绑定面板最终宽 / 高(px) */
6
21
  onResizeEnd?: (size: number) => void;
7
22
  }
8
23
  declare const Split: import("svelte").Component<Props, {}, "">;
@@ -380,6 +380,7 @@
380
380
  --topside-option-padding: var(--topside-margin-md) var(--topside-margin);
381
381
  /* Split token */
382
382
  --topside-dividing-line-color: #CBD5E1;
383
+ --topside-divider-grip-color: #94A3B8;
383
384
  /* Switch Tokens */
384
385
  --topside-switch-width: 100px;
385
386
  --topside-switch-height: 30px;
@@ -779,6 +780,7 @@
779
780
  --topside-progress-step-completed-icon-color: #60A5FA;
780
781
  /* Split token - Dark Theme */
781
782
  --topside-dividing-line-color: #4a4a4a;
783
+ --topside-divider-grip-color: #8a8a8a;
782
784
  /* Switch token - Dark Theme */
783
785
  --topside-switch-color-text-unchecked: #9CA3AF;
784
786
  --topside-switch-color-text-checked: #FFFFFF;
@@ -44,7 +44,7 @@
44
44
  | [AppTopBar](./app-top-bar/README.md) | `@ticatec/topside-elements/AppTopBar` | [示例](./app-top-bar/example.svelte) | 6 |
45
45
  | [Box](./box/README.md) | `@ticatec/topside-elements/Box` | [示例](./box/example.svelte) | 8 |
46
46
  | [CriteriaField](./criteria-field/README.md) | `@ticatec/topside-elements/CriteriaField` | [示例](./criteria-field/example.svelte) | 7 |
47
- | [FlexForm](./topside-flexi/README.md) | `@ticatec/topside-elements/FlexForm` | [示例](./topside-flexi/example.svelte) | 3 |
47
+ | [FlexForm](./flex-form/README.md) | `@ticatec/topside-elements/FlexForm` | [示例](./flex-form/example.svelte) | 3 |
48
48
  | [FlexRowForm](./flex-row-form/README.md) | `@ticatec/topside-elements/FlexRowForm` | [示例](./flex-row-form/example.svelte) | 4 |
49
49
  | [FormField](./form-field/README.md) | `@ticatec/topside-elements/FormField` | [示例](./form-field/example.svelte) | 11 |
50
50
  | [GridForm](./grid-form/README.md) | `@ticatec/topside-elements/GridForm` | [示例](./grid-form/example.svelte) | 5 |
@@ -100,7 +100,7 @@
100
100
 
101
101
  | 组件 | 导入路径 | 示例 | 属性数 |
102
102
  | --- | --- | --- | ---: |
103
- | [DataTable](./data-table/README.md) | `@ticatec/topside-elements/DataTable` | [示例](./data-table/example.svelte) | 15 |
103
+ | [DataTable](./data-table/README.md) | `@ticatec/topside-elements/DataTable` | [示例](./data-table/example.svelte) | 17 |
104
104
 
105
105
  ## 数据展示
106
106
 
@@ -120,7 +120,7 @@
120
120
  | [Icon](./icon/README.md) | `@ticatec/topside-elements/Icon` | [示例](./icon/example.svelte) | 8 |
121
121
  | [IconButton](./icon-button/README.md) | `@ticatec/topside-elements/IconButton` | [示例](./icon-button/example.svelte) | 12 |
122
122
  | [Separator](./separator/README.md) | `@ticatec/topside-elements/Separator` | [示例](./separator/example.svelte) | 2 |
123
- | [Split](./split/README.md) | `@ticatec/topside-elements/Split` | [示例](./split/example.svelte) | 5 |
123
+ | [Split](./split/README.md) | `@ticatec/topside-elements/Split` | [示例](./split/example.svelte) | 10 |
124
124
  | [Tag](./tag/README.md) | `@ticatec/topside-elements/Tag` | [示例](./tag/example.svelte) | 10 |
125
125
  | [TextButton](./text-button/README.md) | `@ticatec/topside-elements/TextButton` | [示例](./text-button/example.svelte) | 13 |
126
126
 
@@ -192,9 +192,9 @@ export default interface DataColumn {
192
192
  hint?: CellHint;
193
193
 
194
194
  /**
195
- * 渲染器
195
+ * 自定义单元格渲染器(可为 Svelte 组件或 `{ component, props }` 配置对象)
196
196
  */
197
- render?: any;
197
+ render?: ColumnRender;
198
198
 
199
199
  /**
200
200
  *
@@ -215,6 +215,19 @@ export default interface DataColumn {
215
215
  * 获取表格的样式
216
216
  */
217
217
  cellStyle?: GetCellStyle;
218
+
219
+ /**
220
+ * 合计行里本列的聚合方式:sum / avg / count / min / max / 自定义函数;
221
+ * 'label' 表示在本列显示合计标签(没有指示列时用)。
222
+ * 只要有一列设置了(或 table 的 summaryValues 覆盖了某列),表格底部就会出现合计行。
223
+ */
224
+ summary?: SummaryKind | SummaryFn | 'label';
225
+
226
+ /**
227
+ * 合计值的格式化。不设时:count 显示整数(千分位);其它先用 formatter,再退回数字自动格式化
228
+ * (千分位,小数位取本列数据最大小数位,avg 至少 2 位)
229
+ */
230
+ summaryFormatter?: (value: any, rows: Array<any>) => string;
218
231
  }
219
232
  ```
220
233
 
@@ -236,6 +249,14 @@ export type HrefBuilder = (item: any) => Array<HrefLink> | HrefLink | string;
236
249
  export type CellHint = (value: any) => string;
237
250
  ```
238
251
 
252
+ ### `ColumnRender`
253
+
254
+ ```ts
255
+ export type ColumnRender<Props extends Record<string, any> = Record<string, any>> =
256
+ | Component<any>
257
+ | ColumnRenderObject<Props>;
258
+ ```
259
+
239
260
  ### `CompareFn`
240
261
 
241
262
  ```ts
@@ -248,6 +269,18 @@ export type CompareFn = (o1: any, o2: any) => number;
248
269
  export type GetCellStyle = (data: any) => string;
249
270
  ```
250
271
 
272
+ ### `SummaryKind`
273
+
274
+ ```ts
275
+ export type SummaryKind = 'sum' | 'avg' | 'count' | 'min' | 'max';
276
+ ```
277
+
278
+ ### `SummaryFn`
279
+
280
+ ```ts
281
+ export type SummaryFn = (values: Array<any>, rows: Array<any>) => any;
282
+ ```
283
+
251
284
  ### `HrefLink`
252
285
 
253
286
  ```ts
@@ -263,6 +296,23 @@ export default interface HrefLink {
263
296
  }
264
297
  ```
265
298
 
299
+ ### `ColumnRenderObject`
300
+
301
+ ```ts
302
+ export interface ColumnRenderObject<Props extends Record<string, any> = Record<string, any>> {
303
+ /**
304
+ * 自定义 Svelte 单元格组件。
305
+ * 组件会接收 `props` 中传入的属性,并自动接收当前行的数据 `data`。
306
+ */
307
+ component: Component<any>;
308
+
309
+ /**
310
+ * 传递给自定义组件的额外属性
311
+ */
312
+ props?: Props;
313
+ }
314
+ ```
315
+
266
316
  ### `OnHrefClick`
267
317
 
268
318
  ```ts
@@ -36,6 +36,8 @@ import DataTable from '@ticatec/topside-elements/DataTable';
36
36
  | `style` | 否 | `string` | 否 | `''` | 否 | 附加到组件根元素的内联样式。 |
37
37
  | `virtual` | 否 | `boolean` | 否 | `false` | 否 | 行虚拟化(窗口化渲染):仅渲染可视区域附近的行,大数据集(数千行以上) 建议开启。要求所有行高度一致(即统一的 rowHeight),因此默认关闭。 |
38
38
  | `overscan` | 否 | `number` | 否 | `8` | 否 | 虚拟化时可视区上下额外渲染的行数。 |
39
+ | `summaryLabel` | 否 | `string` | 否 | `undefined` | 否 | 合计行的标签,默认取 i18n(uniface.dataTable.summaryLabel,"合计" / "Total");传空串则不显示标签 |
40
+ | `summaryValues` | 否 | `Record<string, any> \| null` | 否 | `null` | 否 | 外部提供的合计值,按列 field 取值,优先于列的 summary 在当前 list 上的计算。 分页场景下用它显示服务端算好的全量合计(数字串会按数字格式化)。 |
39
41
 
40
42
  > 组件的“可绑定”表示源码使用了 Svelte 5 的 `$bindable`;服务的“响应式”表示成员使用了 `$state`。复杂类型请同时参考 TypeScript 自动补全。
41
43
 
@@ -143,9 +145,9 @@ export default interface DataColumn {
143
145
  hint?: CellHint;
144
146
 
145
147
  /**
146
- * 渲染器
148
+ * 自定义单元格渲染器(可为 Svelte 组件或 `{ component, props }` 配置对象)
147
149
  */
148
- render?: any;
150
+ render?: ColumnRender;
149
151
 
150
152
  /**
151
153
  *
@@ -166,6 +168,19 @@ export default interface DataColumn {
166
168
  * 获取表格的样式
167
169
  */
168
170
  cellStyle?: GetCellStyle;
171
+
172
+ /**
173
+ * 合计行里本列的聚合方式:sum / avg / count / min / max / 自定义函数;
174
+ * 'label' 表示在本列显示合计标签(没有指示列时用)。
175
+ * 只要有一列设置了(或 table 的 summaryValues 覆盖了某列),表格底部就会出现合计行。
176
+ */
177
+ summary?: SummaryKind | SummaryFn | 'label';
178
+
179
+ /**
180
+ * 合计值的格式化。不设时:count 显示整数(千分位);其它先用 formatter,再退回数字自动格式化
181
+ * (千分位,小数位取本列数据最大小数位,avg 至少 2 位)
182
+ */
183
+ summaryFormatter?: (value: any, rows: Array<any>) => string;
169
184
  }
170
185
  ```
171
186
 
@@ -265,6 +280,14 @@ export type HrefBuilder = (item: any) => Array<HrefLink> | HrefLink | string;
265
280
  export type CellHint = (value: any) => string;
266
281
  ```
267
282
 
283
+ ### `ColumnRender`
284
+
285
+ ```ts
286
+ export type ColumnRender<Props extends Record<string, any> = Record<string, any>> =
287
+ | Component<any>
288
+ | ColumnRenderObject<Props>;
289
+ ```
290
+
268
291
  ### `CompareFn`
269
292
 
270
293
  ```ts
@@ -277,6 +300,18 @@ export type CompareFn = (o1: any, o2: any) => number;
277
300
  export type GetCellStyle = (data: any) => string;
278
301
  ```
279
302
 
303
+ ### `SummaryKind`
304
+
305
+ ```ts
306
+ export type SummaryKind = 'sum' | 'avg' | 'count' | 'min' | 'max';
307
+ ```
308
+
309
+ ### `SummaryFn`
310
+
311
+ ```ts
312
+ export type SummaryFn = (values: Array<any>, rows: Array<any>) => any;
313
+ ```
314
+
280
315
  ### `GetRowActions`
281
316
 
282
317
  ```ts
@@ -317,6 +352,23 @@ export default interface HrefLink {
317
352
  }
318
353
  ```
319
354
 
355
+ ### `ColumnRenderObject`
356
+
357
+ ```ts
358
+ export interface ColumnRenderObject<Props extends Record<string, any> = Record<string, any>> {
359
+ /**
360
+ * 自定义 Svelte 单元格组件。
361
+ * 组件会接收 `props` 中传入的属性,并自动接收当前行的数据 `data`。
362
+ */
363
+ component: Component<any>;
364
+
365
+ /**
366
+ * 传递给自定义组件的额外属性
367
+ */
368
+ props?: Props;
369
+ }
370
+ ```
371
+
320
372
  ### `RowAction`
321
373
 
322
374
  ```ts
@@ -348,12 +400,6 @@ export type OnHrefClick = () => void;
348
400
  export type ButtonType = 'default' | 'primary' | 'secondary' | 'third' | 'forth';
349
401
  ```
350
402
 
351
- ### `ActionHandler`
352
-
353
- ```ts
354
- export type ActionHandler<T = any> = (data: T) => void | Promise<void>;
355
- ```
356
-
357
403
  ## 示例
358
404
 
359
405
  完整示例见 [example.svelte](./example.svelte)。
@@ -21,11 +21,16 @@ import Split from '@ticatec/topside-elements/Split';
21
21
 
22
22
  | 属性 | 重要 | 类型 | 必填 | 默认值 | 可绑定/响应式 | 说明 |
23
23
  | --- | --- | --- | --- | --- | --- | --- |
24
- | `direction` | 否 | `"horizontal" \| "vertical"` | 否 | `"vertical"` | 否 | 可选配置,具体取值范围和数据结构见类型列。 |
25
- | `width` | 否 | `number` | 否 | `2` | 否 | 宽度配置,数值单位为像素。 |
26
- | `bindingPanel` | 否 | `HTMLElement` | 否 | `undefined` | 否 | 可选配置,具体取值范围和数据结构见类型列。 |
27
- | `reverse` | 否 | `boolean` | 否 | `false` | 否 | 可选配置,具体取值范围和数据结构见类型列。 |
28
- | `onResizeEnd` | 否 | `(size: number) => void` | 否 | `undefined` | 否 | 事件或行为回调;参数与返回值以类型签名为准。 |
24
+ | `direction` | 否 | `"horizontal" \| "vertical"` | 否 | `"vertical"` | 否 | 分隔线朝向:vertical 竖线(左右分栏),horizontal 横线(上下分栏) |
25
+ | `width` | 否 | `number` | 否 | `1` | 否 | 可见线条粗细(px),占布局,默认 1 |
26
+ | `bindingPanel` | 否 | `HTMLElement` | 否 | `undefined` | 否 | 被拖动调整尺寸的面板;不传则为固定分隔线 |
27
+ | `reverse` | 否 | `boolean` | 否 | `false` | 否 | 绑定面板在分隔线右侧 / 下方时设为 true |
28
+ | `color` | 否 | `string` | 否 | `undefined` | 否 | 线条底色,默认取 --topside-dividing-line-color |
29
+ | `gripColor` | 否 | `string` | 否 | `undefined` | 否 | 悬停 / 拖动时中间把手的颜色,默认取 --topside-divider-grip-color |
30
+ | `gripSize` | 否 | `number` | 否 | `3` | 否 | 把手粗细(px),默认 3 |
31
+ | `gripLength` | 否 | `number` | 否 | `32` | 否 | 把手长度(px),默认 32;传 0 表示整条加粗 |
32
+ | `hitSize` | 否 | `number` | 否 | `8` | 否 | 鼠标感应区厚度(px),不占布局,默认 8 |
33
+ | `onResizeEnd` | 否 | `(size: number) => void` | 否 | `undefined` | 否 | 拖动结束时回调,参数为绑定面板最终宽 / 高(px) |
29
34
 
30
35
  > 组件的“可绑定”表示源码使用了 Svelte 5 的 `$bindable`;服务的“响应式”表示成员使用了 `$state`。复杂类型请同时参考 TypeScript 自动补全。
31
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ticatec/topside-elements",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A comprehensive UI component library for Svelte applications with rich form controls, data tables, layouts and interactive elements",
5
5
  "keywords": [
6
6
  "svelte",