@bi-digital/react 0.12.0 → 0.12.2

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 +1 @@
1
- {"version":3,"file":"avatar.mjs","names":[],"sources":["../../../src/components/avatar/avatar.tsx"],"sourcesContent":["import { Fragment, type HTMLAttributes, type Ref } from 'react';\nimport clsx from 'clsx/lite';\nimport { Slot } from '@radix-ui/react-slot';\nimport type { DefaultProps } from '../../types';\nimport type { MergeRight } from '../../utilities';\n\nexport type AvatarProps = MergeRight<\n DefaultProps & HTMLAttributes<HTMLSpanElement>,\n {\n /**\n * Name of the person the avatar represents.\n */\n 'aria-label': string;\n /**\n * Size of the avatar.\n * @default 'medium'\n */\n size?: 'small' | 'medium';\n ref?: Ref<HTMLSpanElement>;\n }\n>;\n\n/**\n * An Avatar represents a person.\n *\n * @example\n * <Avatar aria-label=\"Ola Nordmann\">ON</Avatar>\n *\n * @example\n * <Avatar aria-label=\"Ola Nordmann\">\n * <img src='…' alt='Ola Nordmann' />\n * </Avatar>\n */\nexport const Avatar = ({ 'aria-label': ariaLabel, className, ref, size, children, ...props }: AvatarProps) => {\n const isSlotInUse = children && typeof children !== 'string';\n const Component = isSlotInUse ? Slot : Fragment;\n return (\n <span\n className={clsx('bi-avatar', className)}\n aria-label={ariaLabel}\n role='img'\n data-size={size}\n ref={ref}\n {...props}\n >\n <Component {...(isSlotInUse ? { 'aria-hidden': true } : {})}>{children}</Component>\n </span>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAiCA,MAAa,UAAU,EAAE,cAAc,WAAW,WAAW,KAAK,MAAM,UAAU,GAAG,YAAyB;CAC5G,MAAM,cAAc,YAAY,OAAO,aAAa;CACpD,MAAM,YAAY,cAAc,OAAO;CACvC,OACE,oBAAC,QAAD;EACE,WAAW,KAAK,aAAa,UAAU;EACvC,cAAY;EACZ,MAAK;EACL,aAAW;EACN;EACL,GAAI;YAEJ,oBAAC,WAAD;GAAW,GAAK,cAAc,EAAE,eAAe,MAAM,GAAG,EAAE;GAAI;GAAqB,CAAA;EAC9E,CAAA"}
1
+ {"version":3,"file":"avatar.mjs","names":[],"sources":["../../../src/components/avatar/avatar.tsx"],"sourcesContent":["import { Fragment, type HTMLAttributes, type Ref } from 'react';\nimport clsx from 'clsx/lite';\nimport { Slot } from '@radix-ui/react-slot';\nimport type { DefaultProps } from '../../types';\nimport type { MergeRight } from '../../utilities';\n\nexport type AvatarProps = MergeRight<\n DefaultProps & HTMLAttributes<HTMLSpanElement>,\n {\n /**\n * Name of the person the avatar represents.\n */\n 'aria-label': string;\n /**\n * Size of the avatar.\n * @default 'medium'\n */\n size?: 'small' | 'medium';\n ref?: Ref<HTMLSpanElement>;\n }\n>;\n\n/**\n * An Avatar represents a person.\n *\n * @example\n * <Avatar aria-label=\"Ola Nordmann\">ON</Avatar>\n *\n * @example\n * <Avatar aria-label=\"Ola Nordmann\">\n * <img src='…' alt='Ola Nordmann' />\n * </Avatar>\n */\nexport const Avatar = ({ 'aria-label': ariaLabel, className, ref, size, children, ...props }: AvatarProps) => {\n const isSlotInUse = children && typeof children !== 'string';\n const Component = isSlotInUse ? Slot : Fragment;\n return (\n <span\n className={clsx('bi-avatar', className)}\n aria-label={ariaLabel}\n // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role\n role='img'\n data-size={size}\n ref={ref}\n {...props}\n >\n <Component {...(isSlotInUse ? { 'aria-hidden': true } : {})}>{children}</Component>\n </span>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAiCA,MAAa,UAAU,EAAE,cAAc,WAAW,WAAW,KAAK,MAAM,UAAU,GAAG,YAAyB;CAC5G,MAAM,cAAc,YAAY,OAAO,aAAa;CACpD,MAAM,YAAY,cAAc,OAAO;CACvC,OACE,oBAAC,QAAD;EACE,WAAW,KAAK,aAAa,UAAU;EACvC,cAAY;EAEZ,MAAK;EACL,aAAW;EACN;EACL,GAAI;YAEJ,oBAAC,WAAD;GAAW,GAAK,cAAc,EAAE,eAAe,MAAM,GAAG,EAAE;GAAI;GAAqB,CAAA;EAC9E,CAAA"}
@@ -9,13 +9,21 @@ const SubNavbar = ({ children, className, ref, ...props }) => {
9
9
  const elementRef = useRef(null);
10
10
  const fixed = useIsElementAboveViewport(elementRef);
11
11
  const scrollDirection = useScrollDirection();
12
+ /**
13
+ * A sub-navbar can only become fixed by scrolling down past its origin, so default the
14
+ * direction to 'down' until the scroll tracker reports one. Without this, there is a window
15
+ * on the first scroll (the element becomes fixed after ~1px, but the direction only registers
16
+ * after the 5px threshold) where data-fixed is set without data-scroll-dir, which drops the
17
+ * sub-navbar to top:0 underneath a fixed, transparent navbar so both are visible at once.
18
+ */
19
+ const scrollDir = fixed ? scrollDirection ?? "down" : void 0;
12
20
  return /* @__PURE__ */ jsxs("div", {
13
21
  className: "bi-sub-navbar--wrapper",
14
22
  children: [/* @__PURE__ */ jsx("div", { ref: elementRef }), /* @__PURE__ */ jsx("nav", {
15
23
  className: clsx("bi-sub-navbar", className),
16
24
  "data-color": "primary",
17
25
  "data-fixed": fixed,
18
- "data-scroll-dir": fixed ? scrollDirection : void 0,
26
+ "data-scroll-dir": scrollDir,
19
27
  ...props,
20
28
  ref,
21
29
  children
@@ -1 +1 @@
1
- {"version":3,"file":"sub-navbar.mjs","names":[],"sources":["../../../src/components/navbar/sub-navbar.tsx"],"sourcesContent":["import clsx from 'clsx/lite';\nimport { useRef, type HTMLAttributes, type Ref } from 'react';\nimport type { MergeRight } from '../../utilities';\nimport type { DefaultProps } from '../../types';\nimport { useIsElementAboveViewport, useScrollDirection } from '../../utilities/hooks';\n\nexport type SubNavbarProps = MergeRight<\n DefaultProps & HTMLAttributes<HTMLDivElement>,\n {\n ref?: Ref<HTMLElement>;\n }\n>;\n\nexport const SubNavbar = ({ children, className, ref, ...props }: SubNavbarProps) => {\n const elementRef = useRef<HTMLDivElement>(null);\n const fixed = useIsElementAboveViewport(elementRef);\n const scrollDirection = useScrollDirection();\n\n return (\n <div className='bi-sub-navbar--wrapper'>\n <div ref={elementRef} />\n <nav\n className={clsx('bi-sub-navbar', className)}\n data-color='primary'\n data-fixed={fixed}\n data-scroll-dir={fixed ? scrollDirection : undefined}\n {...props}\n ref={ref}\n >\n {children}\n </nav>\n </div>\n );\n};\n"],"mappings":";;;;;;;AAaA,MAAa,aAAa,EAAE,UAAU,WAAW,KAAK,GAAG,YAA4B;CACnF,MAAM,aAAa,OAAuB,KAAK;CAC/C,MAAM,QAAQ,0BAA0B,WAAW;CACnD,MAAM,kBAAkB,oBAAoB;CAE5C,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,oBAAC,OAAD,EAAK,KAAK,YAAc,CAAA,EACxB,oBAAC,OAAD;GACE,WAAW,KAAK,iBAAiB,UAAU;GAC3C,cAAW;GACX,cAAY;GACZ,mBAAiB,QAAQ,kBAAkB,KAAA;GAC3C,GAAI;GACC;GAEJ;GACG,CAAA,CACF"}
1
+ {"version":3,"file":"sub-navbar.mjs","names":[],"sources":["../../../src/components/navbar/sub-navbar.tsx"],"sourcesContent":["import clsx from 'clsx/lite';\nimport { useRef, type HTMLAttributes, type Ref } from 'react';\nimport type { MergeRight } from '../../utilities';\nimport type { DefaultProps } from '../../types';\nimport { useIsElementAboveViewport, useScrollDirection } from '../../utilities/hooks';\n\nexport type SubNavbarProps = MergeRight<\n DefaultProps & HTMLAttributes<HTMLDivElement>,\n {\n ref?: Ref<HTMLElement>;\n }\n>;\n\nexport const SubNavbar = ({ children, className, ref, ...props }: SubNavbarProps) => {\n const elementRef = useRef<HTMLDivElement>(null);\n const fixed = useIsElementAboveViewport(elementRef);\n const scrollDirection = useScrollDirection();\n\n /**\n * A sub-navbar can only become fixed by scrolling down past its origin, so default the\n * direction to 'down' until the scroll tracker reports one. Without this, there is a window\n * on the first scroll (the element becomes fixed after ~1px, but the direction only registers\n * after the 5px threshold) where data-fixed is set without data-scroll-dir, which drops the\n * sub-navbar to top:0 underneath a fixed, transparent navbar so both are visible at once.\n */\n const scrollDir = fixed ? (scrollDirection ?? 'down') : undefined;\n\n return (\n <div className='bi-sub-navbar--wrapper'>\n <div ref={elementRef} />\n <nav\n className={clsx('bi-sub-navbar', className)}\n data-color='primary'\n data-fixed={fixed}\n data-scroll-dir={scrollDir}\n {...props}\n ref={ref}\n >\n {children}\n </nav>\n </div>\n );\n};\n"],"mappings":";;;;;;;AAaA,MAAa,aAAa,EAAE,UAAU,WAAW,KAAK,GAAG,YAA4B;CACnF,MAAM,aAAa,OAAuB,KAAK;CAC/C,MAAM,QAAQ,0BAA0B,WAAW;CACnD,MAAM,kBAAkB,oBAAoB;;;;;;;;CAS5C,MAAM,YAAY,QAAS,mBAAmB,SAAU,KAAA;CAExD,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,oBAAC,OAAD,EAAK,KAAK,YAAc,CAAA,EACxB,oBAAC,OAAD;GACE,WAAW,KAAK,iBAAiB,UAAU;GAC3C,cAAW;GACX,cAAY;GACZ,mBAAiB;GACjB,GAAI;GACC;GAEJ;GACG,CAAA,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"switch.mjs","names":[],"sources":["../../../src/components/switch/switch.tsx"],"sourcesContent":["import type { ReactNode, Ref } from 'react';\n\nimport type { LabelRequiredProps } from '../../types';\nimport type { MergeRight } from '../../utilities';\nimport { Field, type FieldProps } from '../field';\nimport { Input, type InputProps } from '../input';\nimport { Label } from '../label';\n\nexport type SwitchProps = MergeRight<\n LabelRequiredProps & Omit<InputProps, 'type' | 'role' | 'size'> & Pick<FieldProps, 'position'>,\n {\n /** Label */\n label?: ReactNode;\n /** Add error indication to the switch. */\n error?: boolean;\n /** If true, the switch will take up the full width of the container, and not just \"fit-content\" which is default */\n fullwidth?: boolean;\n ref?: Ref<HTMLInputElement>;\n }\n>;\n\nexport const Switch = ({\n label,\n error,\n fullwidth,\n style,\n className,\n ref,\n id,\n 'data-color': color,\n 'data-color-scheme': colorScheme,\n position = 'end',\n ...props\n}: SwitchProps) => {\n return (\n <Field\n className={className}\n data-color={color}\n data-color-scheme={colorScheme}\n style={style}\n fullwidth={fullwidth}\n position={position}\n asChild\n >\n <label>\n <Input role='switch' type='checkbox' id={id} ref={ref} aria-invalid={error || undefined} {...props} />\n {label && (\n <Label weight='medium' htmlFor={id} asChild>\n <span>{label}</span>\n </Label>\n )}\n </label>\n </Field>\n );\n};\n"],"mappings":";;;;;;;;AAqBA,MAAa,UAAU,EACrB,OACA,OACA,WACA,OACA,WACA,KACA,IACA,cAAc,OACd,qBAAqB,aACrB,WAAW,OACX,GAAG,YACc;CACjB,OACE,oBAAC,OAAD;EACa;EACX,cAAY;EACZ,qBAAmB;EACZ;EACI;EACD;EACV,SAAA;YAEA,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,OAAD;GAAO,MAAK;GAAS,MAAK;GAAe;GAAS;GAAK,gBAAc,SAAS,KAAA;GAAW,GAAI;GAAS,CAAA,EACrG,SACC,oBAAC,OAAD;GAAO,QAAO;GAAS,SAAS;GAAI,SAAA;aAClC,oBAAC,QAAD,EAAA,UAAO,OAAa,CAAA;GACd,CAAA,CAEJ,EAAA,CAAA;EACF,CAAA"}
1
+ {"version":3,"file":"switch.mjs","names":[],"sources":["../../../src/components/switch/switch.tsx"],"sourcesContent":["import type { ReactNode, Ref } from 'react';\n\nimport type { LabelRequiredProps } from '../../types';\nimport type { MergeRight } from '../../utilities';\nimport { Field, type FieldProps } from '../field';\nimport { Input, type InputProps } from '../input';\nimport { Label } from '../label';\n\nexport type SwitchProps = MergeRight<\n LabelRequiredProps & Omit<InputProps, 'type' | 'role' | 'size'> & Pick<FieldProps, 'position'>,\n {\n /** Label */\n label?: ReactNode;\n /** Add error indication to the switch. */\n error?: boolean;\n /** If true, the switch will take up the full width of the container, and not just \"fit-content\" which is default */\n fullwidth?: boolean;\n ref?: Ref<HTMLInputElement>;\n }\n>;\n\nexport const Switch = ({\n label,\n error,\n fullwidth,\n style,\n className,\n ref,\n id,\n 'data-color': color,\n 'data-color-scheme': colorScheme,\n position = 'end',\n ...props\n}: SwitchProps) => {\n return (\n <Field\n className={className}\n data-color={color}\n data-color-scheme={colorScheme}\n style={style}\n fullwidth={fullwidth}\n position={position}\n asChild\n >\n <label>\n {/* oxlint-disable-next-line jsx-a11y/role-has-required-aria-props */}\n <Input role='switch' type='checkbox' id={id} ref={ref} aria-invalid={error || undefined} {...props} />\n {label && (\n <Label weight='medium' htmlFor={id} asChild>\n <span>{label}</span>\n </Label>\n )}\n </label>\n </Field>\n );\n};\n"],"mappings":";;;;;;;;AAqBA,MAAa,UAAU,EACrB,OACA,OACA,WACA,OACA,WACA,KACA,IACA,cAAc,OACd,qBAAqB,aACrB,WAAW,OACX,GAAG,YACc;CACjB,OACE,oBAAC,OAAD;EACa;EACX,cAAY;EACZ,qBAAmB;EACZ;EACI;EACD;EACV,SAAA;YAEA,qBAAC,SAAD,EAAA,UAAA,CAEE,oBAAC,OAAD;GAAO,MAAK;GAAS,MAAK;GAAe;GAAS;GAAK,gBAAc,SAAS,KAAA;GAAW,GAAI;GAAS,CAAA,EACrG,SACC,oBAAC,OAAD;GAAO,QAAO;GAAS,SAAS;GAAI,SAAA;aAClC,oBAAC,QAAD,EAAA,UAAO,OAAa,CAAA;GACd,CAAA,CAEJ,EAAA,CAAA;EACF,CAAA"}
package/dist/main.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { r as useCheckboxGroup, t as UseCheckboxGroupOptions } from "./useCheckboxGroup-Boc_tLJ3.mjs";
2
- import { t as useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect-B2JpCiYW.mjs";
2
+ import { t as useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect-DXCY5AxR.mjs";
3
3
  import { $ as Footer, $t as Checkbox, A as SelectfieldProps, At as PopoverProps, B as RadioProps, Bt as DialogContentProps, C as Switch, Cn as Alert, Ct as DropdownButton, D as SkipLink, Dt as PopoverTrigger, E as SpinnerProps, Et as DropdownProps, F as Select, Ft as DialogFooter, G as SubNavbar, Gt as DetailsContent, H as PaginationButtonProps, Ht as DialogProps, I as SelectProps, It as DialogFooterProps, J as NavbarProps, Jt as DetailsProps, K as SubNavbarProps, Kt as DetailsContentProps, L as Search, Lt as DialogHeader, M as SelectOptgroupProps, Mt as DialogCloseButtonProps, N as SelectOption, Nt as DialogTrigger, O as SkipLinkProps, Ot as PopoverTriggerProps, P as SelectOptionProps, Pt as DialogTriggerProps, Q as LinkProps, Qt as CheckboxSubsectionProps, R as SearchProps, Rt as DialogHeaderProps, S as TabsProps, Sn as AvatarProps, St as DropdownTriggerProps, T as Spinner, Tt as Dropdown, U as Pagination, Ut as DetailsSummary, V as PaginationButton, Vt as Dialog, W as PaginationProps, Wt as DetailsSummaryProps, X as LogoProps, Xt as ChipProps, Y as Logo, Yt as Chip, Z as Link, Zt as CheckboxSubsection, _ as Tab, _n as Button, _t as Field, a as ValidationMessage, an as CardMedia, at as Label, b as Tabs, bn as BadgeProps, bt as DropdownTriggerContextProps, c as TooltipProps, cn as CardHeaderProps, ct as FieldsetDescriptionProps, d as Textarea, dn as CardAction, dt as FieldAffix, en as CheckboxProps, et as FooterProps, f as TextareaProps, fn as CardActionProps, ft as FieldAffixProps, g as TabsListProps, gn as CardProps, gt as FieldDescriptionProps, h as TabsList, hn as Card, ht as FieldDescription, i as usePagination, in as CardDateProps, it as FieldsetLegendProps, j as SelectOptgroup, jt as DialogCloseButton, k as Selectfield, kt as Popover, l as Textfield, ln as CardContent, lt as Fieldset, m as TabsPanelProps, mn as CardActionsProps, mt as FieldAffixesProps, n as AlertFooterProps, nn as InputProps, nt as FilterGroupProps, o as ValidationMessageProps, on as CardMediaProps, ot as LabelProps, p as TabsPanel, pn as CardActions, pt as FieldAffixes, q as Navbar, qt as Details, r as UsePaginationProps, rn as CardDate, rt as FieldsetLegend, s as Tooltip, sn as CardHeader, st as FieldsetDescription, t as AlertFooter, tn as Input, tt as FilterGroup, u as TextfieldProps, un as CardContentProps, ut as FieldsetProps, v as TabProps, vn as ButtonProps, vt as FieldProps, w as SwitchProps, wn as AlertProps, wt as DropdownButtonProps, x as TabsContext, xn as Avatar, xt as DropdownTrigger, yn as Badge, yt as DropdownTriggerContext, z as Radio, zt as DialogContent } from "./alert-footer-Yb_zpMhd.mjs";
4
4
  import { n as DialogTriggerContext, r as DialogTriggerContextProps, t as Context } from "./dialog-trigger-context-DbUhxoNL.mjs";
5
5
  import { n as PopoverTriggerContext, r as PopoverTriggerContextProps } from "./popover-trigger-context-BLjAozBn.mjs";
@@ -0,0 +1,7 @@
1
+ import { useEffect } from "react";
2
+
3
+ //#region src/utilities/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.d.ts
4
+ declare const useIsomorphicLayoutEffect: typeof useEffect;
5
+ //#endregion
6
+ export { useIsomorphicLayoutEffect as t };
7
+ //# sourceMappingURL=useIsomorphicLayoutEffect-DXCY5AxR.d.mts.map
@@ -1,5 +1,5 @@
1
1
  import { r as useCheckboxGroup, t as UseCheckboxGroupOptions } from "../../useCheckboxGroup-Boc_tLJ3.mjs";
2
- import { t as useIsomorphicLayoutEffect } from "../../useIsomorphicLayoutEffect-B2JpCiYW.mjs";
2
+ import { t as useIsomorphicLayoutEffect } from "../../useIsomorphicLayoutEffect-DXCY5AxR.mjs";
3
3
  import { i as usePagination, r as UsePaginationProps } from "../../alert-footer-Yb_zpMhd.mjs";
4
4
  import { t as useIsElementAboveViewport } from "../../useIsElementAboveViewport-Cxt9TwLS.mjs";
5
5
  import { t as useScrollDirection } from "../../useScrollDirection-6pq5a8B3.mjs";
@@ -1,2 +1,2 @@
1
- import { t as useIsomorphicLayoutEffect } from "../../../useIsomorphicLayoutEffect-B2JpCiYW.mjs";
1
+ import { t as useIsomorphicLayoutEffect } from "../../../useIsomorphicLayoutEffect-DXCY5AxR.mjs";
2
2
  export { useIsomorphicLayoutEffect };
@@ -1,5 +1,5 @@
1
1
  import { r as useCheckboxGroup, t as UseCheckboxGroupOptions } from "../useCheckboxGroup-Boc_tLJ3.mjs";
2
- import { t as useIsomorphicLayoutEffect } from "../useIsomorphicLayoutEffect-B2JpCiYW.mjs";
2
+ import { t as useIsomorphicLayoutEffect } from "../useIsomorphicLayoutEffect-DXCY5AxR.mjs";
3
3
  import { i as usePagination, r as UsePaginationProps } from "../alert-footer-Yb_zpMhd.mjs";
4
4
  import { t as useIsElementAboveViewport } from "../useIsElementAboveViewport-Cxt9TwLS.mjs";
5
5
  import { t as useScrollDirection } from "../useScrollDirection-6pq5a8B3.mjs";
@@ -96,8 +96,8 @@ const initSubNavbar = ({ selector = ".bi-sub-navbar" } = {}) => {
96
96
  const unsubscribeViewport = viewportTracker.subscribe((isAbove) => {
97
97
  navbar.setAttribute("data-fixed", String(isAbove));
98
98
  if (isAbove) {
99
- const currentDirection = scrollTracker.getCurrentDirection();
100
- if (currentDirection) navbar.setAttribute("data-scroll-dir", currentDirection);
99
+ const currentDirection = scrollTracker.getCurrentDirection() ?? "down";
100
+ navbar.setAttribute("data-scroll-dir", currentDirection);
101
101
  } else navbar.removeAttribute("data-scroll-dir");
102
102
  });
103
103
  const unsubscribeScroll = scrollTracker.subscribe((direction) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/vanilla/sub-navbar/index.ts"],"sourcesContent":["/**\n * Detects the vertical scroll direction of the page.\n * Returns 'up' | 'down' | null. The direction is only updated if the scroll distance\n * exceeds a 5px threshold to prevent sensitivity to minor scroll jitter.\n */\nclass ScrollDirectionTracker {\n private scrollDirection: 'up' | 'down' | null = null;\n private lastScrollY = window.pageYOffset;\n private callbacks = new Set<(direction: 'up' | 'down' | null) => void>();\n\n constructor() {\n this.updateScrollDirection = this.updateScrollDirection.bind(this);\n window.addEventListener('scroll', this.updateScrollDirection);\n }\n\n private updateScrollDirection() {\n const scrollY = window.pageYOffset;\n const direction = scrollY > this.lastScrollY ? 'down' : 'up';\n\n if (direction !== this.scrollDirection && (scrollY - this.lastScrollY > 5 || scrollY - this.lastScrollY < -5)) {\n this.scrollDirection = direction;\n this.callbacks.forEach((callback) => callback(this.scrollDirection));\n }\n\n this.lastScrollY = scrollY > 0 ? scrollY : 0;\n }\n\n subscribe(callback: (direction: 'up' | 'down' | null) => void) {\n this.callbacks.add(callback);\n return () => this.callbacks.delete(callback);\n }\n\n destroy() {\n window.removeEventListener('scroll', this.updateScrollDirection);\n this.callbacks.clear();\n }\n\n getCurrentDirection() {\n return this.scrollDirection;\n }\n}\n\n/**\n * Detects when an element is scrolled past and is no longer in the viewport.\n */\nclass ElementAboveViewportTracker {\n private isAboveViewport = false;\n private observer: IntersectionObserver;\n private callbacks = new Set<(isAbove: boolean) => void>();\n\n constructor(private element: HTMLElement) {\n this.observer = new IntersectionObserver(\n ([entry]) => {\n const newIsAbove = entry.boundingClientRect.y < 0;\n if (newIsAbove !== this.isAboveViewport) {\n this.isAboveViewport = newIsAbove;\n this.callbacks.forEach((callback) => callback(this.isAboveViewport));\n }\n },\n { threshold: [0] },\n );\n\n this.observer.observe(this.element);\n }\n\n subscribe(callback: (isAbove: boolean) => void) {\n this.callbacks.add(callback);\n return () => this.callbacks.delete(callback);\n }\n\n destroy() {\n this.observer.disconnect();\n this.callbacks.clear();\n }\n\n getIsAboveViewport() {\n return this.isAboveViewport;\n }\n}\n\nexport type InitSubNavbarOptions = {\n /**\n * Custom selector to find sub-navbars.\n * @default '.bi-sub-navbar'\n */\n selector?: string;\n};\n\n/**\n * Initializes all sub-navbars on the page to handle scroll-based behavior.\n * This includes:\n * - Setting data-fixed attribute when the navbar is scrolled past\n * - Setting data-scroll-dir attribute based on scroll direction when fixed\n *\n * @param options Configuration options\n * @returns Cleanup function to remove all event listeners and observers\n */\nexport const initSubNavbar = ({ selector = '.bi-sub-navbar' }: InitSubNavbarOptions = {}) => {\n const subNavbars = document.querySelectorAll<HTMLElement>(selector);\n\n if (subNavbars.length === 0) {\n console.warn(`No elements found with selector: ${selector}`);\n return () => {};\n }\n\n const scrollTracker = new ScrollDirectionTracker();\n const cleanupFunctions: (() => void)[] = [];\n\n subNavbars.forEach((navbar) => {\n const wrapper = navbar.closest('.bi-sub-navbar--wrapper') as HTMLElement;\n if (!wrapper) {\n console.warn('Sub-navbar must be wrapped in an element with class \"bi-sub-navbar--wrapper\"');\n return;\n }\n\n // Find or create the reference element (empty div) that acts as the scroll trigger\n let referenceElement = wrapper.querySelector<HTMLElement>('div:first-child');\n if (!referenceElement || referenceElement === navbar) {\n referenceElement = document.createElement('div');\n wrapper.insertBefore(referenceElement, navbar);\n }\n\n const viewportTracker = new ElementAboveViewportTracker(referenceElement);\n\n // Subscribe to viewport changes\n const unsubscribeViewport = viewportTracker.subscribe((isAbove) => {\n navbar.setAttribute('data-fixed', String(isAbove));\n\n // Only set scroll direction when fixed\n if (isAbove) {\n const currentDirection = scrollTracker.getCurrentDirection();\n if (currentDirection) {\n navbar.setAttribute('data-scroll-dir', currentDirection);\n }\n } else {\n navbar.removeAttribute('data-scroll-dir');\n }\n });\n\n // Subscribe to scroll direction changes\n const unsubscribeScroll = scrollTracker.subscribe((direction) => {\n const isFixed = navbar.getAttribute('data-fixed') === 'true';\n if (isFixed && direction) {\n navbar.setAttribute('data-scroll-dir', direction);\n }\n });\n\n cleanupFunctions.push(() => {\n unsubscribeViewport();\n unsubscribeScroll();\n viewportTracker.destroy();\n });\n });\n\n // Return cleanup function\n return () => {\n cleanupFunctions.forEach((cleanup) => cleanup());\n scrollTracker.destroy();\n };\n};\n"],"mappings":";;;;;;AAKA,IAAM,yBAAN,MAA6B;CAC3B,kBAAgD;CAChD,cAAsB,OAAO;CAC7B,4BAAoB,IAAI,KAAgD;CAExE,cAAc;EACZ,KAAK,wBAAwB,KAAK,sBAAsB,KAAK,KAAK;EAClE,OAAO,iBAAiB,UAAU,KAAK,sBAAsB;;CAG/D,wBAAgC;EAC9B,MAAM,UAAU,OAAO;EACvB,MAAM,YAAY,UAAU,KAAK,cAAc,SAAS;EAExD,IAAI,cAAc,KAAK,oBAAoB,UAAU,KAAK,cAAc,KAAK,UAAU,KAAK,cAAc,KAAK;GAC7G,KAAK,kBAAkB;GACvB,KAAK,UAAU,SAAS,aAAa,SAAS,KAAK,gBAAgB,CAAC;;EAGtE,KAAK,cAAc,UAAU,IAAI,UAAU;;CAG7C,UAAU,UAAqD;EAC7D,KAAK,UAAU,IAAI,SAAS;EAC5B,aAAa,KAAK,UAAU,OAAO,SAAS;;CAG9C,UAAU;EACR,OAAO,oBAAoB,UAAU,KAAK,sBAAsB;EAChE,KAAK,UAAU,OAAO;;CAGxB,sBAAsB;EACpB,OAAO,KAAK;;;;;;AAOhB,IAAM,8BAAN,MAAkC;CAKZ;CAJpB,kBAA0B;CAC1B;CACA,4BAAoB,IAAI,KAAiC;CAEzD,YAAY,SAA8B;EAAtB,KAAA,UAAA;EAClB,KAAK,WAAW,IAAI,sBACjB,CAAC,WAAW;GACX,MAAM,aAAa,MAAM,mBAAmB,IAAI;GAChD,IAAI,eAAe,KAAK,iBAAiB;IACvC,KAAK,kBAAkB;IACvB,KAAK,UAAU,SAAS,aAAa,SAAS,KAAK,gBAAgB,CAAC;;KAGxE,EAAE,WAAW,CAAC,EAAE,EAAE,CACnB;EAED,KAAK,SAAS,QAAQ,KAAK,QAAQ;;CAGrC,UAAU,UAAsC;EAC9C,KAAK,UAAU,IAAI,SAAS;EAC5B,aAAa,KAAK,UAAU,OAAO,SAAS;;CAG9C,UAAU;EACR,KAAK,SAAS,YAAY;EAC1B,KAAK,UAAU,OAAO;;CAGxB,qBAAqB;EACnB,OAAO,KAAK;;;;;;;;;;;;AAqBhB,MAAa,iBAAiB,EAAE,WAAW,qBAA2C,EAAE,KAAK;CAC3F,MAAM,aAAa,SAAS,iBAA8B,SAAS;CAEnE,IAAI,WAAW,WAAW,GAAG;EAC3B,QAAQ,KAAK,oCAAoC,WAAW;EAC5D,aAAa;;CAGf,MAAM,gBAAgB,IAAI,wBAAwB;CAClD,MAAM,mBAAmC,EAAE;CAE3C,WAAW,SAAS,WAAW;EAC7B,MAAM,UAAU,OAAO,QAAQ,0BAA0B;EACzD,IAAI,CAAC,SAAS;GACZ,QAAQ,KAAK,iFAA+E;GAC5F;;EAIF,IAAI,mBAAmB,QAAQ,cAA2B,kBAAkB;EAC5E,IAAI,CAAC,oBAAoB,qBAAqB,QAAQ;GACpD,mBAAmB,SAAS,cAAc,MAAM;GAChD,QAAQ,aAAa,kBAAkB,OAAO;;EAGhD,MAAM,kBAAkB,IAAI,4BAA4B,iBAAiB;EAGzE,MAAM,sBAAsB,gBAAgB,WAAW,YAAY;GACjE,OAAO,aAAa,cAAc,OAAO,QAAQ,CAAC;GAGlD,IAAI,SAAS;IACX,MAAM,mBAAmB,cAAc,qBAAqB;IAC5D,IAAI,kBACF,OAAO,aAAa,mBAAmB,iBAAiB;UAG1D,OAAO,gBAAgB,kBAAkB;IAE3C;EAGF,MAAM,oBAAoB,cAAc,WAAW,cAAc;GAE/D,IADgB,OAAO,aAAa,aAAa,KAAK,UACvC,WACb,OAAO,aAAa,mBAAmB,UAAU;IAEnD;EAEF,iBAAiB,WAAW;GAC1B,qBAAqB;GACrB,mBAAmB;GACnB,gBAAgB,SAAS;IACzB;GACF;CAGF,aAAa;EACX,iBAAiB,SAAS,YAAY,SAAS,CAAC;EAChD,cAAc,SAAS"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/vanilla/sub-navbar/index.ts"],"sourcesContent":["/**\n * Detects the vertical scroll direction of the page.\n * Returns 'up' | 'down' | null. The direction is only updated if the scroll distance\n * exceeds a 5px threshold to prevent sensitivity to minor scroll jitter.\n */\nclass ScrollDirectionTracker {\n private scrollDirection: 'up' | 'down' | null = null;\n private lastScrollY = window.pageYOffset;\n private callbacks = new Set<(direction: 'up' | 'down' | null) => void>();\n\n constructor() {\n this.updateScrollDirection = this.updateScrollDirection.bind(this);\n window.addEventListener('scroll', this.updateScrollDirection);\n }\n\n private updateScrollDirection() {\n const scrollY = window.pageYOffset;\n const direction = scrollY > this.lastScrollY ? 'down' : 'up';\n\n if (direction !== this.scrollDirection && (scrollY - this.lastScrollY > 5 || scrollY - this.lastScrollY < -5)) {\n this.scrollDirection = direction;\n this.callbacks.forEach((callback) => callback(this.scrollDirection));\n }\n\n this.lastScrollY = scrollY > 0 ? scrollY : 0;\n }\n\n subscribe(callback: (direction: 'up' | 'down' | null) => void) {\n this.callbacks.add(callback);\n return () => this.callbacks.delete(callback);\n }\n\n destroy() {\n window.removeEventListener('scroll', this.updateScrollDirection);\n this.callbacks.clear();\n }\n\n getCurrentDirection() {\n return this.scrollDirection;\n }\n}\n\n/**\n * Detects when an element is scrolled past and is no longer in the viewport.\n */\nclass ElementAboveViewportTracker {\n private isAboveViewport = false;\n private observer: IntersectionObserver;\n private callbacks = new Set<(isAbove: boolean) => void>();\n\n constructor(private element: HTMLElement) {\n this.observer = new IntersectionObserver(\n ([entry]) => {\n const newIsAbove = entry.boundingClientRect.y < 0;\n if (newIsAbove !== this.isAboveViewport) {\n this.isAboveViewport = newIsAbove;\n this.callbacks.forEach((callback) => callback(this.isAboveViewport));\n }\n },\n { threshold: [0] },\n );\n\n this.observer.observe(this.element);\n }\n\n subscribe(callback: (isAbove: boolean) => void) {\n this.callbacks.add(callback);\n return () => this.callbacks.delete(callback);\n }\n\n destroy() {\n this.observer.disconnect();\n this.callbacks.clear();\n }\n\n getIsAboveViewport() {\n return this.isAboveViewport;\n }\n}\n\nexport type InitSubNavbarOptions = {\n /**\n * Custom selector to find sub-navbars.\n * @default '.bi-sub-navbar'\n */\n selector?: string;\n};\n\n/**\n * Initializes all sub-navbars on the page to handle scroll-based behavior.\n * This includes:\n * - Setting data-fixed attribute when the navbar is scrolled past\n * - Setting data-scroll-dir attribute based on scroll direction when fixed\n *\n * @param options Configuration options\n * @returns Cleanup function to remove all event listeners and observers\n */\nexport const initSubNavbar = ({ selector = '.bi-sub-navbar' }: InitSubNavbarOptions = {}) => {\n const subNavbars = document.querySelectorAll<HTMLElement>(selector);\n\n if (subNavbars.length === 0) {\n console.warn(`No elements found with selector: ${selector}`);\n return () => {};\n }\n\n const scrollTracker = new ScrollDirectionTracker();\n const cleanupFunctions: (() => void)[] = [];\n\n subNavbars.forEach((navbar) => {\n const wrapper = navbar.closest('.bi-sub-navbar--wrapper') as HTMLElement;\n if (!wrapper) {\n console.warn('Sub-navbar must be wrapped in an element with class \"bi-sub-navbar--wrapper\"');\n return;\n }\n\n // Find or create the reference element (empty div) that acts as the scroll trigger\n let referenceElement = wrapper.querySelector<HTMLElement>('div:first-child');\n if (!referenceElement || referenceElement === navbar) {\n referenceElement = document.createElement('div');\n wrapper.insertBefore(referenceElement, navbar);\n }\n\n const viewportTracker = new ElementAboveViewportTracker(referenceElement);\n\n // Subscribe to viewport changes\n const unsubscribeViewport = viewportTracker.subscribe((isAbove) => {\n navbar.setAttribute('data-fixed', String(isAbove));\n\n // Only set scroll direction when fixed\n if (isAbove) {\n // A sub-navbar can only become fixed by scrolling down past its origin, so default the\n // direction to 'down' until the tracker reports one. Without this, there is a window on\n // the first scroll (it becomes fixed after ~1px, but the direction only registers after\n // the 5px threshold) where data-fixed is set without data-scroll-dir, which drops the\n // sub-navbar to top:0 underneath a fixed, transparent navbar so both are visible at once.\n const currentDirection = scrollTracker.getCurrentDirection() ?? 'down';\n navbar.setAttribute('data-scroll-dir', currentDirection);\n } else {\n navbar.removeAttribute('data-scroll-dir');\n }\n });\n\n // Subscribe to scroll direction changes\n const unsubscribeScroll = scrollTracker.subscribe((direction) => {\n const isFixed = navbar.getAttribute('data-fixed') === 'true';\n if (isFixed && direction) {\n navbar.setAttribute('data-scroll-dir', direction);\n }\n });\n\n cleanupFunctions.push(() => {\n unsubscribeViewport();\n unsubscribeScroll();\n viewportTracker.destroy();\n });\n });\n\n // Return cleanup function\n return () => {\n cleanupFunctions.forEach((cleanup) => cleanup());\n scrollTracker.destroy();\n };\n};\n"],"mappings":";;;;;;AAKA,IAAM,yBAAN,MAA6B;CAC3B,kBAAgD;CAChD,cAAsB,OAAO;CAC7B,4BAAoB,IAAI,KAAgD;CAExE,cAAc;EACZ,KAAK,wBAAwB,KAAK,sBAAsB,KAAK,KAAK;EAClE,OAAO,iBAAiB,UAAU,KAAK,sBAAsB;;CAG/D,wBAAgC;EAC9B,MAAM,UAAU,OAAO;EACvB,MAAM,YAAY,UAAU,KAAK,cAAc,SAAS;EAExD,IAAI,cAAc,KAAK,oBAAoB,UAAU,KAAK,cAAc,KAAK,UAAU,KAAK,cAAc,KAAK;GAC7G,KAAK,kBAAkB;GACvB,KAAK,UAAU,SAAS,aAAa,SAAS,KAAK,gBAAgB,CAAC;;EAGtE,KAAK,cAAc,UAAU,IAAI,UAAU;;CAG7C,UAAU,UAAqD;EAC7D,KAAK,UAAU,IAAI,SAAS;EAC5B,aAAa,KAAK,UAAU,OAAO,SAAS;;CAG9C,UAAU;EACR,OAAO,oBAAoB,UAAU,KAAK,sBAAsB;EAChE,KAAK,UAAU,OAAO;;CAGxB,sBAAsB;EACpB,OAAO,KAAK;;;;;;AAOhB,IAAM,8BAAN,MAAkC;CAKZ;CAJpB,kBAA0B;CAC1B;CACA,4BAAoB,IAAI,KAAiC;CAEzD,YAAY,SAA8B;EAAtB,KAAA,UAAA;EAClB,KAAK,WAAW,IAAI,sBACjB,CAAC,WAAW;GACX,MAAM,aAAa,MAAM,mBAAmB,IAAI;GAChD,IAAI,eAAe,KAAK,iBAAiB;IACvC,KAAK,kBAAkB;IACvB,KAAK,UAAU,SAAS,aAAa,SAAS,KAAK,gBAAgB,CAAC;;KAGxE,EAAE,WAAW,CAAC,EAAE,EAAE,CACnB;EAED,KAAK,SAAS,QAAQ,KAAK,QAAQ;;CAGrC,UAAU,UAAsC;EAC9C,KAAK,UAAU,IAAI,SAAS;EAC5B,aAAa,KAAK,UAAU,OAAO,SAAS;;CAG9C,UAAU;EACR,KAAK,SAAS,YAAY;EAC1B,KAAK,UAAU,OAAO;;CAGxB,qBAAqB;EACnB,OAAO,KAAK;;;;;;;;;;;;AAqBhB,MAAa,iBAAiB,EAAE,WAAW,qBAA2C,EAAE,KAAK;CAC3F,MAAM,aAAa,SAAS,iBAA8B,SAAS;CAEnE,IAAI,WAAW,WAAW,GAAG;EAC3B,QAAQ,KAAK,oCAAoC,WAAW;EAC5D,aAAa;;CAGf,MAAM,gBAAgB,IAAI,wBAAwB;CAClD,MAAM,mBAAmC,EAAE;CAE3C,WAAW,SAAS,WAAW;EAC7B,MAAM,UAAU,OAAO,QAAQ,0BAA0B;EACzD,IAAI,CAAC,SAAS;GACZ,QAAQ,KAAK,iFAA+E;GAC5F;;EAIF,IAAI,mBAAmB,QAAQ,cAA2B,kBAAkB;EAC5E,IAAI,CAAC,oBAAoB,qBAAqB,QAAQ;GACpD,mBAAmB,SAAS,cAAc,MAAM;GAChD,QAAQ,aAAa,kBAAkB,OAAO;;EAGhD,MAAM,kBAAkB,IAAI,4BAA4B,iBAAiB;EAGzE,MAAM,sBAAsB,gBAAgB,WAAW,YAAY;GACjE,OAAO,aAAa,cAAc,OAAO,QAAQ,CAAC;GAGlD,IAAI,SAAS;IAMX,MAAM,mBAAmB,cAAc,qBAAqB,IAAI;IAChE,OAAO,aAAa,mBAAmB,iBAAiB;UAExD,OAAO,gBAAgB,kBAAkB;IAE3C;EAGF,MAAM,oBAAoB,cAAc,WAAW,cAAc;GAE/D,IADgB,OAAO,aAAa,aAAa,KAAK,UACvC,WACb,OAAO,aAAa,mBAAmB,UAAU;IAEnD;EAEF,iBAAiB,WAAW;GAC1B,qBAAqB;GACrB,mBAAmB;GACnB,gBAAgB,SAAS;IACzB;GACF;CAGF,aAAa;EACX,iBAAiB,SAAS,YAAY,SAAS,CAAC;EAChD,cAAc,SAAS"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bi-digital/react",
3
3
  "type": "module",
4
- "version": "0.12.0",
4
+ "version": "0.12.2",
5
5
  "description": "React components from BI Digital",
6
6
  "author": "BI Digital",
7
7
  "homepage": "https://designsystem.bi.no/",
@@ -41,7 +41,7 @@
41
41
  "@radix-ui/react-slot": "^1.3.0",
42
42
  "@radix-ui/react-use-controllable-state": "^1.2.2",
43
43
  "clsx": "^2.1.1",
44
- "@bi-digital/icons": "0.12.0"
44
+ "@bi-digital/icons": "0.12.2"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/react": "^19.2.14",
@@ -59,7 +59,7 @@
59
59
  "peerDependencies": {
60
60
  "react": "^19",
61
61
  "react-dom": "^19",
62
- "@bi-digital/css": "0.12.0"
62
+ "@bi-digital/css": "0.12.2"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsdown",
@@ -1,7 +0,0 @@
1
- import { useLayoutEffect } from "react";
2
-
3
- //#region src/utilities/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.d.ts
4
- declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
5
- //#endregion
6
- export { useIsomorphicLayoutEffect as t };
7
- //# sourceMappingURL=useIsomorphicLayoutEffect-B2JpCiYW.d.mts.map