forked from mengyxu/noob-components
8 changed files with 278 additions and 3 deletions
@ -0,0 +1,2 @@ |
|||||||
|
{"file": ".claude/commands/trellis/finish-work.md", "reason": "Finish work checklist"} |
||||||
|
{"file": ".claude/commands/trellis/check-frontend.md", "reason": "Frontend check spec"} |
||||||
@ -0,0 +1 @@ |
|||||||
|
{"file": ".claude/commands/trellis/check-frontend.md", "reason": "Frontend check spec"} |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
{"file": ".trellis/workflow.md", "reason": "Project workflow and conventions"} |
||||||
|
{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend development guide"} |
||||||
|
{"file": "packages/base/data/list-table-v2.vue", "reason": "Main component being modified"} |
||||||
|
{"file": "packages/base/item/tzDateTime.vue", "reason": "Referenced for timestamp implementation"} |
||||||
@ -0,0 +1,71 @@ |
|||||||
|
# list-table-v2 Timestamp + Vue JSX |
||||||
|
|
||||||
|
## Goal |
||||||
|
|
||||||
|
Add Vue JSX support to the project and upgrade `list-table-v2.vue` to use `tzDateTime.vue` for timestamp columns with enhanced type support. |
||||||
|
|
||||||
|
## Requirements |
||||||
|
|
||||||
|
### 1. Enable Vue JSX |
||||||
|
- Install `@vitejs/plugin-vue-jsx` package |
||||||
|
- Add JSX plugin to `vite.config.ts` |
||||||
|
|
||||||
|
### 2. Enhanced Timestamp Type |
||||||
|
|
||||||
|
Update `TableColumn.timestamp` type from `boolean` to: |
||||||
|
```typescript |
||||||
|
undefined | boolean | string | { |
||||||
|
valueFormat?: string; |
||||||
|
valueTz?: string; |
||||||
|
displayFormat?: string; |
||||||
|
locale?: string; |
||||||
|
type?: "iso8601" | "unix" | "unixMillis"; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Behavior:** |
||||||
|
- `undefined` or falsy → column is not a timestamp |
||||||
|
- `true` → equivalent to `{ type: 'unixMillis', displayFormat: 'YYYY-MM-DD HH:mm:ss' }` |
||||||
|
- `string` → pass to either `type` or `valueFormat` depending on the value |
||||||
|
- Otherwise → pass as props to `TzDateTime` |
||||||
|
|
||||||
|
### 3. Use tzDateTime.vue for Timestamp Columns |
||||||
|
- Import `TzDateTime` component in `list-table-v2.vue` |
||||||
|
- Replace inline `formatStamp` with `TzDateTime` component via JSX rendering |
||||||
|
- Ensure dayjs plugins are loaded when needed |
||||||
|
|
||||||
|
## Technical Notes |
||||||
|
|
||||||
|
### Files to Modify |
||||||
|
1. `vite.config.ts` - Add JSX plugin |
||||||
|
2. `packages/base/data/list-table-v2.vue` - Update timestamp handling |
||||||
|
|
||||||
|
### tzDateTime Props Interface |
||||||
|
```typescript |
||||||
|
interface Props { |
||||||
|
value: string | number; |
||||||
|
valueFormat?: string; |
||||||
|
valueTz?: string; |
||||||
|
displayFormat?: string; |
||||||
|
locale?: string; |
||||||
|
type?: "iso8601" | "unix" | "unixMillis"; |
||||||
|
slot?: boolean; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### Conversion Logic |
||||||
|
``` |
||||||
|
timestamp value → TzDateTime props: |
||||||
|
- timestamp=true → { type: 'unixMillis', displayFormat: 'YYYY-MM-DD HH:mm:ss' } |
||||||
|
- timestamp='unix' → { type: 'unix' } (shorthand) |
||||||
|
- timestamp='YYYY-MM-DD' → { valueFormat: 'YYYY-MM-DD' } |
||||||
|
- timestamp={ type: 'iso8601' } → passed directly |
||||||
|
``` |
||||||
|
|
||||||
|
## Acceptance Criteria |
||||||
|
- [ ] `@vitejs/plugin-vue-jsx` installed |
||||||
|
- [ ] `vite.config.ts` includes JSX plugin |
||||||
|
- [ ] `TableColumn` interface updated with new timestamp type |
||||||
|
- [ ] `list-table-v2.vue` uses `TzDateTime` for timestamp columns |
||||||
|
- [ ] Backward compatible with existing `timestamp={true}` usage |
||||||
|
- [ ] TypeScript compiles without errors |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
{ |
||||||
|
"id": "list-table-v2-timestamp-jsx", |
||||||
|
"name": "list-table-v2-timestamp-jsx", |
||||||
|
"title": "list-table-v2 timestamp + vue jsx", |
||||||
|
"description": "", |
||||||
|
"status": "planning", |
||||||
|
"dev_type": null, |
||||||
|
"scope": null, |
||||||
|
"priority": "P2", |
||||||
|
"creator": "hechang27-sprt", |
||||||
|
"assignee": "hechang27-sprt", |
||||||
|
"createdAt": "2026-03-25", |
||||||
|
"completedAt": null, |
||||||
|
"branch": null, |
||||||
|
"base_branch": "dev", |
||||||
|
"worktree_path": null, |
||||||
|
"current_phase": 0, |
||||||
|
"next_action": [ |
||||||
|
{ |
||||||
|
"phase": 1, |
||||||
|
"action": "implement" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"phase": 2, |
||||||
|
"action": "check" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"phase": 3, |
||||||
|
"action": "finish" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"phase": 4, |
||||||
|
"action": "create-pr" |
||||||
|
} |
||||||
|
], |
||||||
|
"commit": null, |
||||||
|
"pr_url": null, |
||||||
|
"subtasks": [], |
||||||
|
"children": [], |
||||||
|
"parent": null, |
||||||
|
"relatedFiles": [], |
||||||
|
"notes": "", |
||||||
|
"meta": {} |
||||||
|
} |
||||||
@ -0,0 +1,120 @@ |
|||||||
|
# Plan: Viewport Resize Handling for list-table-v2 |
||||||
|
|
||||||
|
## 1. Problem Analysis |
||||||
|
|
||||||
|
### Current State |
||||||
|
- The `list-table-v2` component uses a probe row technique to dynamically measure row height when `rowHeight` is NOT explicitly set (dynamic height mode) |
||||||
|
- The `measureProbeRow` function (lines 217-231) already exists and correctly: |
||||||
|
- Clears `estimatedRowHeight.value` before re-measuring |
||||||
|
- Uses TWO `requestAnimationFrame` calls after `nextTick` to let el-table-v2 fully settle |
||||||
|
- Measures the probe row's `offsetHeight` |
||||||
|
|
||||||
|
### Problem |
||||||
|
- When the viewport width changes, column widths may adjust (due to `flexGrow: 1` in `tableColumns` computed) |
||||||
|
- This can cause row heights to change, but currently there is no trigger to re-measure |
||||||
|
- Without re-measuring, the `estimatedRowHeight` may become inaccurate, causing layout issues |
||||||
|
|
||||||
|
### Constraints (from previous attempts) |
||||||
|
1. ResizeObserver on container caused errors in component setup - AVOID |
||||||
|
2. Must use TWO `requestAnimationFrame` calls after `nextTick` for proper el-table-v2 settlement |
||||||
|
3. Must clear `estimatedRowHeight` before re-measuring for el-table-v2 to recalculate properly |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 2. Implementation Approach |
||||||
|
|
||||||
|
### Strategy |
||||||
|
Add a debounced window resize event listener that: |
||||||
|
1. Checks if dynamic height mode is active (`shouldUseProbeRow`) |
||||||
|
2. Calls the existing `measureProbeRow` function |
||||||
|
3. Cleans up properly on component unmount |
||||||
|
|
||||||
|
### Why Window Resize Instead of ResizeObserver |
||||||
|
- `el-auto-resizer` already handles container size changes internally |
||||||
|
- Window resize is the correct trigger because column widths change based on viewport width (via `flexGrow`) |
||||||
|
- ResizeObserver on container caused setup errors in previous attempts |
||||||
|
|
||||||
|
### Debounce Strategy |
||||||
|
- Use lodash's `debounce` (already imported as `lodash-es`) |
||||||
|
- Debounce delay: ~200-300ms to avoid excessive recalculations during active resize |
||||||
|
- The debounced function will be created with `onMounted` and cleaned up with `onUnmounted` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 3. Code Changes Needed |
||||||
|
|
||||||
|
### File: `/home/hechang27/Documents/sprt/noob-components/packages/base/data/list-table-v2.vue` |
||||||
|
|
||||||
|
#### Import Additions (line 53) |
||||||
|
```typescript |
||||||
|
import { ref, computed, watch, h, onMounted, onUpdated, onUnmounted, useSlots, renderSlot, nextTick } from "vue"; |
||||||
|
``` |
||||||
|
|
||||||
|
#### New State Variable |
||||||
|
After line 66 (`const estimatedRowHeight = ref<number | undefined>(undefined);`): |
||||||
|
```typescript |
||||||
|
// Debounced resize handler for re-measuring probe row |
||||||
|
const debouncedResizeHandler = ref<() => void>(); |
||||||
|
``` |
||||||
|
|
||||||
|
#### New Lifecycle Hooks |
||||||
|
After line 214 (after the existing `watch` block): |
||||||
|
|
||||||
|
```typescript |
||||||
|
// Setup resize listener on mount |
||||||
|
onMounted(() => { |
||||||
|
// Create debounced handler |
||||||
|
debouncedResizeHandler.value = lodash.debounce(() => { |
||||||
|
// Only re-measure in dynamic height mode |
||||||
|
if (shouldUseProbeRow.value) { |
||||||
|
measureProbeRow(); |
||||||
|
} |
||||||
|
}, 250); // 250ms debounce delay |
||||||
|
|
||||||
|
// Attach window resize listener |
||||||
|
window.addEventListener('resize', debouncedResizeHandler.value); |
||||||
|
}); |
||||||
|
|
||||||
|
// Cleanup on unmount |
||||||
|
onUnmounted(() => { |
||||||
|
if (debouncedResizeHandler.value) { |
||||||
|
window.removeEventListener('resize', debouncedResizeHandler.value); |
||||||
|
debouncedResizeHandler.value.cancel(); // Cancel any pending debounce calls |
||||||
|
} |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 4. Verification Steps |
||||||
|
|
||||||
|
### Manual Testing |
||||||
|
1. Open a page using `list-table-v2` without explicit `rowHeight` prop |
||||||
|
2. Open browser DevTools and inspect a row element to see its height |
||||||
|
3. Resize the browser window (change width) |
||||||
|
4. Verify that: |
||||||
|
- No console errors occur during resize |
||||||
|
- The row height is recalculated correctly after resize settles |
||||||
|
- The table layout remains correct after resize |
||||||
|
|
||||||
|
### Edge Cases to Verify |
||||||
|
1. **Rapid resize**: Resize window quickly back and forth - should not cause errors or excessive measurements |
||||||
|
2. **Minimum width**: Table should handle minimum width gracefully without breaking |
||||||
|
3. **No explicit rowHeight**: Verify resize handling is NOT active when `rowHeight` IS set (fixed height mode) |
||||||
|
4. **Unmount cleanup**: Navigate away from the page and verify no memory leaks or hanging listeners |
||||||
|
|
||||||
|
### Code Review Checklist |
||||||
|
- [ ] `onUnmounted` is imported and used |
||||||
|
- [ ] Debounced handler is properly cancelled on unmount |
||||||
|
- [ ] `shouldUseProbeRow.value` check prevents unnecessary calls in fixed-height mode |
||||||
|
- [ ] Two RAF frames are used in `measureProbeRow` (existing pattern preserved) |
||||||
|
- [ ] `estimatedRowHeight` is cleared before re-measuring (existing pattern preserved) |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 5. Dependencies |
||||||
|
|
||||||
|
- lodash-es debounce (already imported) |
||||||
|
- Vue lifecycle hooks (already available) |
||||||
|
|
||||||
|
No new dependencies required. |
||||||
Loading…
Reference in new issue