Skip to content

feat(plugin-gantt): locate icon + flash highlight + group node type (4-level tree) - #1809

Merged
os-zhuang merged 2 commits into
mainfrom
claude/friendly-edison-260ede
Jun 20, 2026
Merged

feat(plugin-gantt): locate icon + flash highlight + group node type (4-level tree)#1809
os-zhuang merged 2 commits into
mainfrom
claude/friendly-edison-260ede

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What

Three Gantt enhancements, driven by a manufacturing-scheduling spec (项目 → 产品 → 排产计划 → 派工单):

  1. Locate-on-timeline icon — each task-list End cell gets a 🞋 (Crosshair) button. Clicking it smooth-scrolls the timeline to center that row's bar and pulses the bar with a ring (gantt-flash) so it's easy to spot after the scroll. The button reveals on row hover; stopPropagation keeps it from opening the detail drawer.

  2. type:'group' node kind — a pure tree header that is expandable/collapsible like a summary row but renders no timeline bar. This makes pure grouping levels (项目/产品: 左侧成树、右侧无条) possible — previously any node with children always drew a summary bar. Children still render their own bars normally.

  3. 4-level demo fixture (?mfg=1) — 项目A → 产品A-1/A-2 → 排产计划 → 派工单(WO001 张三…), exercising group headers + summary bars + task bars together.

Spec coverage

层级节点要求状态
一级 项目type:'group'无条 · 展开/折叠
二级 产品type:'group'无条 · 展开/折叠
三级 排产计划summary有时间条 · 全部甘特操作
四级 派工单task子任务条✅ (有条)

Not in this PR (follow-ups, noted with the requester): seed-able "三级默认折叠" prop, and per-node read-only lock so 派工单 is view-only while 排产计划 stays draggable.

i18n

Adds gantt.row.locate (zh 在甘特图上定位 / en Locate on timeline) plus the standalone fallback in GANTT_DEFAULT_TRANSLATIONS.

Verification

  • pnpm --filter @object-ui/plugin-gantt vitest run188 passed (16 files).
  • Manual (demo ?mfg=1): only 排产计划 rows carry summary bars and only 派工单 rows carry task bars; the 项目/产品 group rows render no bar; collapsing 项目A hides its whole subtree and expands back; locate click centers + flashes the target bar.

Config-driven (not hardcoded)

No business terms live in the component — GanttView only knows the generic primitives parent (builds the tree) and type (node kind). The 4-level manufacturing data lives only in the demo fixture (demo/main.tsx), never in the runtime.

The structure is configured through view metadata, not code:

  • parentField → which record field builds the hierarchy
  • typeField → which record field maps to the node kind. A value of group (or folder) now yields the no-bar header; summary/project/phase keep a bar; milestone/task as before.

So an app models 项目/产品 as records whose typeField = group, and 排产计划/派工单 fall out of the parent chain + start/end fields — no plugin changes needed.

…up node type
- Add a locate (Crosshair) icon to each task-list End cell. Clicking it
smooth-scrolls the timeline to center that row's bar and pulses the bar
with a ring (gantt-flash) so the eye can catch it after the scroll.
- Add `type:'group'` node kind: a pure tree header that's expandable/
collapsible like a summary but renders NO timeline bar — for 项目/产品
style grouping levels (左侧成树、右侧无条). Children render bars normally.
- Demo: add a 4-level manufacturing fixture (?mfg=1) — 项目 → 产品 →
排产计划 → 派工单 — exercising group headers + summary/task bars.
- i18n: add gantt.row.locate (zh/en + gantt default fallback).
@vercel

vercelBot commented Jun 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredJun 19, 2026 4:45pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)58.7 KB350 KB
Entry fileindex-CSo9EW6s.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)5.95KB2.10KB
app-shell (runtime-config.js)4.72KB1.69KB
app-shell (types.js)0.01KB0.04KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)17.16KB3.44KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)4.79KB1.88KB
auth (LoginForm.js)5.11KB1.92KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.63KB2.15KB
auth (SocialSignInButtons.js)8.89KB3.61KB
auth (UserMenu.js)3.40KB1.22KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)24.74KB6.00KB
auth (createAuthenticatedFetch.js)3.33KB1.32KB
auth (index.js)1.75KB0.76KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.01KB0.79KB
auth (useIsWorkspaceAdmin.js)1.28KB0.70KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.42KB0.96KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)413.83KB87.84KB
core (index.js)1.44KB0.53KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)96.65KB24.08KB
fields (index.js)157.75KB37.58KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.32KB0.93KB
i18n (pickLocalized.js)1.31KB0.67KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)21.15KB4.68KB
i18n (useSafeTranslation.js)1.63KB0.57KB
layout (index.js)36.05KB9.94KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)4.42KB1.27KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)1.77KB0.77KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)4.87KB1.77KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.11KB0.87KB
permissions (evaluator.js)4.00KB1.23KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.42KB0.68KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.98KB12.65KB
plugin-charts (index.js)40.52KB11.50KB
plugin-chatbot (index.js)141.13KB34.56KB
plugin-dashboard (index.js)93.22KB22.37KB
plugin-designer (index.js)213.42KB42.94KB
plugin-detail (index.js)193.28KB46.52KB
plugin-editor (index.js)2.38KB1.06KB
plugin-form (index.js)91.08KB22.18KB
plugin-gantt (index.js)121.85KB29.99KB
plugin-grid (index.js)114.50KB30.71KB
plugin-kanban (index.js)48.82KB13.13KB
plugin-list (index.js)92.32KB21.76KB
plugin-map (index.js)16.02KB4.98KB
plugin-markdown (index.js)13.58KB4.64KB
plugin-report (index.js)34.79KB9.13KB
plugin-timeline (index.js)25.37KB7.20KB
plugin-view (index.js)84.13KB20.27KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.55KB0.67KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)14.98KB4.84KB
react (index.js)0.76KB0.42KB
tenant (TenantContext.js)0.31KB0.25KB
tenant (TenantGuard.js)1.04KB0.43KB
tenant (TenantProvider.js)2.76KB0.98KB
tenant (TenantScopedQuery.js)0.77KB0.44KB
tenant (index.js)0.75KB0.38KB
tenant (resolver.js)2.64KB0.76KB
tenant (useTenant.js)0.50KB0.32KB
tenant (useTenantBranding.js)0.62KB0.39KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)0.77KB0.41KB
types (disclosure.js)0.20KB0.18KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (index.js)1.54KB0.68KB
types (layout.js)0.20KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.26KB1.96KB
types (tenant.js)0.20KB0.18KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)0.75KB0.46KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

normalizeTaskType now maps a record's typeField value 'group'/'folder' onto
the no-bar group header (project/phase still map to bar-carrying summary).
This makes the no-bar grouping level configurable via view metadata
(typeField) instead of only via hand-built GanttTask. Adds tests.
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)58.7 KB350 KB
Entry fileindex-YkgRJxsS.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)5.95KB2.10KB
app-shell (runtime-config.js)4.72KB1.69KB
app-shell (types.js)0.01KB0.04KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)17.16KB3.44KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)4.79KB1.88KB
auth (LoginForm.js)5.11KB1.92KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.63KB2.15KB
auth (SocialSignInButtons.js)8.89KB3.61KB
auth (UserMenu.js)3.40KB1.22KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)24.74KB6.00KB
auth (createAuthenticatedFetch.js)3.33KB1.32KB
auth (index.js)1.75KB0.76KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.01KB0.79KB
auth (useIsWorkspaceAdmin.js)1.28KB0.70KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.42KB0.96KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)413.83KB87.84KB
core (index.js)1.44KB0.53KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)96.65KB24.08KB
fields (index.js)157.75KB37.58KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.32KB0.93KB
i18n (pickLocalized.js)1.31KB0.67KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)21.15KB4.68KB
i18n (useSafeTranslation.js)1.63KB0.57KB
layout (index.js)36.05KB9.94KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)4.42KB1.27KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)1.77KB0.77KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)4.87KB1.77KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.11KB0.87KB
permissions (evaluator.js)4.00KB1.23KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.42KB0.68KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.98KB12.65KB
plugin-charts (index.js)40.52KB11.50KB
plugin-chatbot (index.js)141.13KB34.56KB
plugin-dashboard (index.js)93.22KB22.37KB
plugin-designer (index.js)213.42KB42.94KB
plugin-detail (index.js)193.28KB46.52KB
plugin-editor (index.js)2.38KB1.06KB
plugin-form (index.js)91.08KB22.18KB
plugin-gantt (index.js)121.89KB30.00KB
plugin-grid (index.js)114.50KB30.71KB
plugin-kanban (index.js)48.82KB13.13KB
plugin-list (index.js)92.32KB21.76KB
plugin-map (index.js)16.02KB4.98KB
plugin-markdown (index.js)13.58KB4.64KB
plugin-report (index.js)34.79KB9.13KB
plugin-timeline (index.js)25.37KB7.20KB
plugin-view (index.js)84.13KB20.27KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.55KB0.67KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)14.98KB4.84KB
react (index.js)0.76KB0.42KB
tenant (TenantContext.js)0.31KB0.25KB
tenant (TenantGuard.js)1.04KB0.43KB
tenant (TenantProvider.js)2.76KB0.98KB
tenant (TenantScopedQuery.js)0.77KB0.44KB
tenant (index.js)0.75KB0.38KB
tenant (resolver.js)2.64KB0.76KB
tenant (useTenant.js)0.50KB0.32KB
tenant (useTenantBranding.js)0.62KB0.39KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)0.77KB0.41KB
types (disclosure.js)0.20KB0.18KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (index.js)1.54KB0.68KB
types (layout.js)0.20KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.26KB1.96KB
types (tenant.js)0.20KB0.18KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)0.75KB0.46KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-zhuang
os-zhuang merged commit c125273 into mainJun 20, 2026
10 checks passed
@os-zhuang
os-zhuang deleted the claude/friendly-edison-260ede branch June 20, 2026 05:49
baozhoutao added a commit that referenced this pull request Jun 22, 2026
… icon
Update the page-builder gantt section to cover the metadata-driven tree
knobs: typeField 'group'/'folder' (无条 header), lockField (仅查看/跳转
per-node), defaultCollapsedDepth (默认折叠 by depth), and the per-row 定位
(locate) icon. Keeps the objectui skill current with #1809 + #1870.
os-zhuang pushed a commit that referenced this pull request Jun 22, 2026
…ndle ergonomics & view-switch anchoring (#1870)
* feat(plugin-gantt): config-driven node lock + defaultCollapsedDepth
Two metadata-driven knobs for the 项目→产品→排产计划→派工单 tree (no hardcoded
business logic):
- defaultCollapsedDepth (默认折叠): GanttView seeds collapsedIds once from a
0-indexed depth threshold, folding every node at/below it that has children.
ObjectGantt forwards schema.defaultCollapsedDepth.
- locked (仅查看/跳转): per-node view-only flag. A locked bar can't be
dragged/resized, its progress can't be dragged, no dependency dot, and its
inline-edit / context-menu edit+delete are hidden — clicking (open/jump) still
works. ObjectGantt maps schema.lockField truthiness onto task.locked.
Demo: ?mfg=1 locks every 派工单 and collapses 排产计划 (depth 2) by default.
+5 tests (group/depth/lock in GanttView, lockField+depth wiring in ObjectGantt).
* docs(skill): document gantt group/lock/defaultCollapsedDepth + locate icon
Update the page-builder gantt section to cover the metadata-driven tree
knobs: typeField 'group'/'folder' (无条 header), lockField (仅查看/跳转
per-node), defaultCollapsedDepth (默认折叠 by depth), and the per-row 定位
(locate) icon. Keeps the objectui skill current with #1809 + #1870.
* fix(plugin-gantt): flash after locate-scroll settles; pin locate icon flush to row edge
- Flash now starts on scrollend (the smooth-scroll-finished signal) rather
than ~40ms after the click, so the pulse fires where the bar lands instead
of mid-flight. Falls back to a short fixed delay when scrollend is
unsupported; a 1.5s backstop guards the supported path. Pending waits are
torn down on re-trigger and unmount via flashCleanupRef.
- Locate icon moved out of the right-aligned End-date flow into a row-pinned
absolute slot flush with the chart divider, so it no longer reads as inset
('偏左') and no longer shifts the date column on hover.
* fix(plugin-gantt): locate aligns to bar start; locate icon clears the date
- Locate now scrolls to the bar's start (with a small left margin) instead of
centering its midpoint, so a long bar's beginning stays visible rather than
scrolling off the left edge. Bars that fit the viewport still center.
- Fix the locate icon falling back to the row's left edge: Tailwind's
fractional right-0.5 isn't in the prebuilt CSS, so the right offset is now
an inline style (right:1px), keeping the icon flush at the divider with a
small gap from the date column.
* fix(plugin-gantt): make locate flash far more visible
Blink the located bar 3× with a 3px outline ring plus a colored drop-shadow
glow (up to ~9px) instead of a single thin 2px outline-color fade. The glow is
a filter (not box-shadow) so it stays clear of the critical-path bars' inline
box-shadow. Bump the flash duration to 1.5s and sync the clear timer.
* fix(plugin-gantt): show task title inside leaf bars
Leaf task bars previously rendered no label — only summary bars showed their
title, so a wide leaf bar (e.g. a 派工单 work order) looked blank. Render the
title inside leaf bars like summary bars; the progress%/drag-date readout now
overlays it on hover so the bar's text width never shifts. Adds a tree test.
* fix(plugin-gantt): per-key i18n fallback so partial host dictionaries don't leak raw keys
useGanttTranslation probed a single key (gantt.column.taskName) and, if the
host i18n resolved it, trusted the host's t() for *every* key. A consuming app
whose dictionary translates the common gantt keys but lags on newer ones (e.g.
gantt.linkType.*, gantt.menu.removeDependency) then leaked raw keys, since
i18next returns the key string on a miss. Resolve each key against the host
first and fall back to the bundled default only when the host returns the key
unchanged.
* feat(plugin-gantt): make the task-list pane an independent vertical scroller
The left task list was overflow-hidden and only mirrored the timeline's
scrollTop, so vertical scrolling was possible only over the right (timeline)
pane. With large datasets users expect to scroll the task list directly.
Make the list overflow-y-auto and sync two-way: list scroll pushes scrollTop
onto the timeline (which drives the shared virtualization window and mirrors
back). Both sync paths assign scrollTop only when it differs, so the browser
emits no further scroll event and the loop self-terminates.
* refactor(plugin-gantt): make 'Today' a text button to match 'This week'/'This month'
The jump-to-today control was a CalendarDays icon while the sibling
period-jump controls were text buttons, so the toolbar group read
inconsistently. Render it as a text button (gantt.toolbar.today) with the
same sizing as its siblings; drop the now-unused CalendarDays import.
* feat(plugin-gantt): anchor the viewport-centre date when switching granularity
Switching Day/Week/Month/Year kept the scroll container's raw pixel scrollLeft,
but a Day->Month switch shrinks the timeline ~5x, so that offset landed on a
wildly different (usually edge-clamped) date — the jumpy, disorienting behaviour
users described as '乱'. Capture the date at the viewport centre via xToDate
before the switch, then re-centre on it in a useLayoutEffect once the new
dateToX mapping and total width are measured (pre-paint, so no flash). The
anchor ref is null for any other dateToX change (zoom, fold, edits), leaving
those paths untouched.
* demo(plugin-gantt): manufacturing 4-level scheduling fixture
Flesh out the ?mfg=1 fixture to mirror the 3.4 制造排班 spec: status-based
bar colors for 排产计划 (待开始/已下推/进行中/已完成) and 派工单
(待开始/进行中/已报工/已完成), ◆-prefixed milestone rows, planned-vs-actual
baselines on 派工单, hover-detail `fields`, FS dependencies between 三级
排产计划 only, and a status-color legend (ManufacturingLegend).
* fix(plugin-gantt): exact-day anchor across view switches + visible timeline scrollbar
View-switch anchoring: switching granularity now preserves the precise day at
the viewport's left edge, so Day(Apr 9)→Week→Month→Day round-trips back to Apr 9
instead of drifting to the timeline start. The precise intent is held in a
persistent ref updated only by genuine user scrolling; coarser views snap their
*display* to the containing week/month bucket but never overwrite the anchor. A
gate, armed on each switch and cleared only by real user input (wheel / touch /
pointer / scroll keys), blocks the browser's auto-clamp scroll — fired when a
narrower layout shrinks scrollWidth — from poisoning the anchor (the bug that
sent Month→Day to Dec 29).
Scrollbar: macOS overlay scrollbars collapse to 0px and auto-hide, making the
timeline's vertical scrollbar nearly impossible to find, and thousands of
virtualized rows shrank the thumb to a sliver. Style ::-webkit-scrollbar into a
persistent 14px bar with a floored thumb size, and hide the redundant (synced)
task-list scrollbar. Standard scrollbar-width is scoped to @supports
not selector(::-webkit-scrollbar) so modern Chrome doesn't ignore the pseudo
and fall back to the overlay bar.
* feat(plugin-gantt): drag-to-link from either bar endpoint, auto-deriving FS/FF/SS/SF
dhtmlx-style dependency creation. Each leaf bar now exposes a connector
dot at BOTH ends (start + finish). The link type is derived from which
endpoint you drag FROM and which half of the target bar you drop ONTO:
end→start=FS, end→end=FF, start→start=SS, start→end=SF. A draft hint
('Task A (end) → Task B (end)') names the endpoints during the drag so
the resulting link type is obvious before release.
* feat(plugin-gantt): drag the task-list divider to resize the name column
The task-list pane width was auto-derived from the container with no way to
widen it. Add a draggable splitter on the divider between the name grid and
the timeline: drag to resize (clamped 160px ↔ container-200px so it can never
collapse or swallow the chart), double-click to reset to auto-size. The chosen
width persists in the saved layout (persistLayoutKey / onLayoutChange) and the
start/end columns auto-reveal once the pane is wide enough.
* fix(plugin-gantt): separate link/progress handles to stop mis-operation (dhtmlx-style)
The connector dots half-overlapped the bar end and resize zone, and the
progress grip was a thin full-height strip in the bar body — so linking
sometimes moved the bar and progress was awkward to grab.
- Connector dots now float fully OUTSIDE each bar end (detached circles in
a 20px hit area). Sitting outside the bar body means grabbing one can
never start a move or an edge-resize. They show on row hover (or while
this bar is the link source) and are pure visual hints during a drag.
- Progress handle is now an up-pointing triangle hugging the bottom edge at
the progress %, with its hit area confined to the bottom half — clear of
the move body (top) and the centred end dots.
* fix(plugin-gantt): track bar hover on the full-width row so the connector dots stay grabbable
The link connector dots float just outside each end of a bar. Hover was
tracked on the narrow bar element, so the moment the cursor crossed the
bar's right edge toward the end dot, the bar's mouseleave fired, cleared
hoveredTaskId, and the dot vanished before it could be grabbed — and a
fast sweep could outrun the dot's pointer-events:auto re-render and fall
straight through.
Move onMouseEnter/onMouseLeave to the full-width row. The row spans the
whole timeline, so moving bar -> dot never leaves the hover zone
(dhtmlx-style), and the dot is enabled well before the cursor reaches it.
* fix(plugin-gantt): give link dots dhtmlx-style breathing room; widen default columns
Two ergonomics fixes the user called out against dhtmlx:
1. Connector dots were crammed against the bar end. Now the visible circle
sits out with a comfortable ~12px gap so it reads as its own affordance.
The transparent hit area is wider and BRIDGES back to the bar edge (z-20
above the dependency line's z-10 hit-stroke), so crossing the gap never
drops the row hover and the dot stays grabbable the whole way out.
2. Default column width was too narrow (35/50/60). Bumped to 44/64/80 so
day/week/month columns have room to breathe.
Tests: the drag-snap suites were calibrated to columnWidth=60. Rescaled
their pixel deltas to clean multiples of 80 (avoiding the Math.round(±1.5)
asymmetry) and updated the COLUMN_WIDTH constants/comments. 201 pass.
* fix(plugin-gantt): floor timeline columns at 110px so day/week/month stay readable
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@baozhoutao@os-zhuang