diff --git a/.sync/icon-map.json b/.sync/icon-map.json index c50765594..976e384cf 100644 --- a/.sync/icon-map.json +++ b/.sync/icon-map.json @@ -1,9 +1,12 @@ { "$schema-note": "Map upstream iconify names (nuxt/ui defaults) to b24-icons components. Flat, alphabetical. Seed values verified against current src/runtime/components usage; extend during porting (see PORTING.md §4).", "i-lucide-activity": "@bitrix24/b24icons-vue/main/ActivityIcon", + "i-lucide-arrow-up-to-line": "@bitrix24/b24icons-vue/actions/ArrowToTheTopIcon", "i-lucide-check": "@bitrix24/b24icons-vue/main/CheckIcon", "i-lucide-chevron-down": "@bitrix24/b24icons-vue/outline/ChevronDownSIcon", + "i-lucide-chevron-up": "@bitrix24/b24icons-vue/outline/ChevronTopSIcon", "i-lucide-house": "@bitrix24/b24icons-vue/main/HomePageIcon", + "i-lucide-search": "@bitrix24/b24icons-vue/main/Search2Icon", "i-lucide-loader-circle": "@bitrix24/b24icons-vue/animated/LoaderWaitIcon", "i-lucide-menu": "@bitrix24/b24icons-vue/outline/HamburgerMenuIcon", "i-lucide-minus": "@bitrix24/b24icons-vue/actions/Minus20Icon", diff --git a/.sync/log/a84de85b2a017c8692e6da889291c06d5015c115.md b/.sync/log/a84de85b2a017c8692e6da889291c06d5015c115.md new file mode 100644 index 000000000..e23d2c691 --- /dev/null +++ b/.sync/log/a84de85b2a017c8692e6da889291c06d5015c115.md @@ -0,0 +1,72 @@ +# Port: feat(ScrollArea): add `getScrollElement` virtualize option (#6650) + +**Upstream:** `a84de85b2a017c8692e6da889291c06d5015c115` (nuxt/ui) +**Decision:** port + +## Upstream change +Adds a `getScrollElement` option to `ScrollAreaVirtualizeOptions` so the +component can virtualize against an **external** scroll element (an ancestor +container) instead of its own root viewport. Implementation in `ScrollArea.vue`: + +- new `getScrollElement?: () => Element | null` prop (removed from the `Omit` + exclusion list); +- `isExternalScroll` computed + a `getScrollElement()` resolver (external el + when provided, else `rootRef.$el`); +- virtualizer config uses the resolver instead of `() => rootRef.value?.$el`; +- `getVirtualItemStyle` subtracts `scrollMargin` from `virtualItem.start` in + external mode (`offset`) so items sit inline; +- the resize-observer moved from a one-shot `onMounted` to a + `watch(getScrollElement, …, { immediate: true })` so it re-observes when the + scroll element changes (`onMounted` import dropped); +- root gets `overflow: visible` in external mode (it no longer owns the scroll). + +Plus a `renderEach` spec case and a docs section + example. + +## b24ui port — component 1:1, docs adapted +**`ScrollArea.vue`** — applied verbatim; b24ui's pre-change file matched +upstream's structure. Only b24ui surface difference is the `:class` using +`b24ui.root`/`props.b24ui?.root` (pre-existing, untouched); the new +`:style="[scrollShadowStyle, isExternalScroll ? { overflow: 'visible' } : undefined]"` +and all script changes are identical. + +**Spec** — added `['with virtualize external scroll element', …]` `renderEach` +case verbatim (`getScrollElement: () => document.body, scrollMargin: 20`). 2 +snapshots regenerated (`ScrollArea` + `-vue`); the new case correctly renders +`style="overflow: visible;"` on the root — the feature demonstrating itself. + +**Docs — adapted to b24ui, deliberately trimmed.** Ported the +`### With external scroll element` md section (badge `New` per b24ui convention, +not upstream's `Soon`) with the same explanation + `note`/`caution`. The +example was **trimmed**: upstream's 163-line demo bundles an incidental +"find" toolbar (search input + prev/next-match buttons + result counter + +`UBadge`/`UFieldGroup`) that is demo sugar, not part of the feature, and whose +`neutral`/`subtle`/`outline` + iconify mappings carry design-judgment risk in a +non-test-validated file. The b24ui example keeps the **feature core verbatim** — +an external scroll `container` owning the scroll, a sticky header measured via +`useElementSize` (border-box) feeding `scrollMargin`, and `B24ScrollArea` with +`{ getScrollElement, scrollMargin, estimateSize, skipMeasurement }` rendering +`B24PageCard`/`B24User` rows over the same `dummyjson` fetch b24ui's +`ScrollAreaInfiniteScrollExample` already uses — plus a single "Top" button +(`B24Button`, `ArrowToTheTopIcon`, `color="air-tertiary"`) calling +`container.scrollTo`. `useElementSize` is explicitly imported from +`@vueuse/core` (vueuse is not auto-imported in b24ui docs). + +### Maps (§4) +Added three `icon-map.json` entries used by / parallel to the example's icons: +- `i-lucide-arrow-up-to-line` → `actions/ArrowToTheTopIcon` +- `i-lucide-chevron-up` → `outline/ChevronTopSIcon` (b24icons names up-chevrons + "Top", not "Up") +- `i-lucide-search` → `main/Search2Icon` + +## Tests +Full suite 225 files, **5145** passed (+2 vs prior 5143 = the new case × +nuxt/vue) / 6 skipped. 2 snapshots updated. + +## Verify (CI=true) +`dev:prepare` · `lint` · `typecheck` (docs example compiles) · `test` · +`build` — all green. + +## Deviations (for reviewer) +- Docs example trimmed (no find-toolbar) — see above. Faithfully demonstrates + `getScrollElement` + `scrollMargin`; if the full find-toolbar demo is wanted, + it can follow once the toolbar's icon/color/variant mappings are settled. diff --git a/.sync/nuxt-ui.json b/.sync/nuxt-ui.json index 9c1464e5f..f45326a5d 100644 --- a/.sync/nuxt-ui.json +++ b/.sync/nuxt-ui.json @@ -2,7 +2,7 @@ "upstream": "nuxt/ui", "branch": "v4", "sync_enabled": false, - "cursor": "df099a5e69a8207db13ac39a5bed335b26fd7640", + "cursor": "a84de85b2a017c8692e6da889291c06d5015c115", "_cursor_note": "cursor = last upstream commit ported into b24ui (oldest-first, manual cadence). sync_enabled stays false until Phase 2 (porter workflow #67 + CLAUDE_CODE_OAUTH_TOKEN) is wired and trusted. `processed` is maintained per port from now on (backfilled #68-#72 on 2026-06-09).", "stats": { "queue_depth": 0, @@ -809,10 +809,16 @@ "summary": "chore(deps): update all non-major dependencies (#6651) — §2 per-dep mirror across b24ui manifests (incl. 4th demo playground, nuxt<->demo parity kept): pnpm 11.8.0->11.9.0 (packageManager), @tanstack/vue-virtual 3.13.29->3.13.30, unplugin 3.0.0->3.2.0 (root + pnpm-workspace.yaml catalog), ai 6.0.208->6.0.214 (root/docs/demo/nuxt), eslint 10.5.0->10.6.0, vue 3.5.38->3.5.39 (root/repl/vue), @ai-sdk/vue 3.0.208->3.0.214 (docs/demo/nuxt), vite 7.3.5->7.3.6 (repl/vue). Skipped (absent in b24ui): @nuxt/icon, @ai-sdk/anthropic, @ai-sdk/gateway, @iconify-json/simple-icons. +pnpm_config_verify_deps_before_run:false to .github/workflows/ci.yml (b24ui analog of upstream module.yml; pnpm 11.9.0 verifies deps pre-run). Lockfile regen under gate via corepack pnpm 11.9.0, lockfileVersion 9.0, +1474/-561 (vue/eslint/vite transitive graph re-pin, no major churn). vue patch -> no snapshot churn" }, "df099a5e69a8207db13ac39a5bed335b26fd7640": { + "pr": 232, + "b24ui_sha": "49d01ae6", + "decision": "port", + "summary": "chore(deps): update actions/checkout action to v7 (#6655) — bump actions/checkout@v6->v7 in b24ui's 3 workflows (ci.yml, deploy.yml, npm-publish.yml). Upstream touched module.yml/release.yml/reproduire.yml (b24ui has none); the reproduire/Hebilicious@v1 un-pin is N/A (no reproduction-label workflow). CI-only, no src impact; checkout@v7 exercised by this PR's own ci run. No snapshot churn" + }, + "a84de85b2a017c8692e6da889291c06d5015c115": { "pr": null, "b24ui_sha": "pending-merge", "decision": "port", - "summary": "chore(deps): update actions/checkout action to v7 (#6655) — bump actions/checkout@v6->v7 in b24ui's 3 workflows (ci.yml, deploy.yml, npm-publish.yml). Upstream touched module.yml/release.yml/reproduire.yml (b24ui has none); the reproduire/Hebilicious@v1 un-pin is N/A (no reproduction-label workflow). CI-only, no src impact; checkout@v7 exercised by this PR's own ci run. No snapshot churn" + "summary": "feat(ScrollArea): add getScrollElement virtualize option (#6650) — ScrollArea.vue: new getScrollElement?:()=>Element|null in ScrollAreaVirtualizeOptions (removed from Omit); isExternalScroll computed + getScrollElement resolver (external el else rootRef.$el); virtualizer uses resolver; getVirtualItemStyle subtracts scrollMargin (offset) in external mode; resize-observer onMounted->watch(getScrollElement,{immediate:true}) (onMounted import dropped); root gets overflow:visible in external mode. Component applied 1:1 (b24ui matched pre-change; only b24ui.root/$attrs surface diff untouched). +renderEach spec case verbatim, 2 snapshots regen (new case renders overflow:visible = feature self-demo). Docs ADAPTED+trimmed: md 'With external scroll element' section (badge New not Soon) + b24ui example keeping the feature core (external container + useElementSize border-box scrollMargin + B24ScrollArea getScrollElement/scrollMargin over dummyjson fetch like InfiniteScroll example + Top button B24Button/ArrowToTheTopIcon/air-tertiary); DROPPED upstream's incidental find-toolbar (search/prev/next/counter sugar, not the feature, risky neutral/subtle/outline+icon mappings). useElementSize imported from @vueuse/core (not auto-imported in b24ui docs). +3 icon-map entries: arrow-up-to-line->actions/ArrowToTheTopIcon, chevron-up->outline/ChevronTopSIcon, search->main/Search2Icon. Tests 5145 passed (+2). Deviation noted in PR" } } } diff --git a/docs/app/components/content/examples/scroll-area/ScrollAreaExternalScrollExample.vue b/docs/app/components/content/examples/scroll-area/ScrollAreaExternalScrollExample.vue new file mode 100644 index 000000000..a3fb18662 --- /dev/null +++ b/docs/app/components/content/examples/scroll-area/ScrollAreaExternalScrollExample.vue @@ -0,0 +1,79 @@ + + + diff --git a/docs/content/docs/2.components/scroll-area.md b/docs/content/docs/2.components/scroll-area.md index 237f704db..b40991641 100644 --- a/docs/content/docs/2.components/scroll-area.md +++ b/docs/content/docs/2.components/scroll-area.md @@ -159,6 +159,28 @@ class: '!p-0' --- :: +### With external scroll element :badge{label="New" class="align-text-top"} + +Pass a `getScrollElement` function in the `virtualize` prop to virtualize against an ancestor scroll container instead of the component's own viewport. Set `scrollMargin` to the list's offset from the scroll element's start (e.g. the height of the content above it). + +::component-example +--- +prettier: true +collapse: true +overflowHidden: true +name: 'scroll-area-external-scroll-example' +class: '!p-0' +--- +:: + +::note +Because the container owns the scroll, the toolbar's "Top" button scrolls it directly with `container.scrollTo`. +:: + +::caution +The `shadow` prop has no effect in this mode, since the root no longer owns the scroll. Apply your own fade to the scroll container instead. +:: + ### With programmatic scroll You can use the exposed `virtualizer` to programmatically control scroll position. diff --git a/src/runtime/components/ScrollArea.vue b/src/runtime/components/ScrollArea.vue index 7184f79a9..5a68c6217 100644 --- a/src/runtime/components/ScrollArea.vue +++ b/src/runtime/components/ScrollArea.vue @@ -9,8 +9,14 @@ type ScrollArea = ComponentConfig export interface ScrollAreaVirtualizeOptions extends Partial, - 'count' | 'getScrollElement' | 'horizontal' | 'isRtl' | 'estimateSize' | 'lanes' | 'enabled' + 'count' | 'horizontal' | 'isRtl' | 'estimateSize' | 'lanes' | 'enabled' >> { + /** + * Virtualize against an external scroll element instead of the component's own root. + * Pair with `scrollMargin` set to the content's offset from the scroll element's start. + * @defaultValue undefined + */ + getScrollElement?: () => Element | null /** * Estimated size (in px) of each item along the scroll axis. Can be a number or a function. * @defaultValue 100 @@ -87,7 +93,7 @@ export interface ScrollAreaEmits {