@jirawatpyk/aura-react 5.79.0 → 5.81.0

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/README.md CHANGED
@@ -100,7 +100,69 @@ export function Providers({ children, locale }: { children: React.ReactNode; loc
100
100
  // app/layout.tsx (a Server Component): <body><Providers locale="th">{children}</Providers></body>
101
101
  ```
102
102
 
103
- `linkComponent` is used by Button `href`, Breadcrumb, Tabs and Menu items with `href`, BottomNav, Pagination `getHref` (page numbers and, since 4.17, the previous / next arrows), Stat `href`, SideNav and DataTable row/pager links. Each of them also takes its own `linkComponent`, which wins over the provider's. Pass `getHref` from a client component (it's a function too). The Next.js starter does all of this, and CI clicks every AURA link in it to check none triggers a full page load. Only `th` shows Buddhist-era years; `en` and `sv` are Gregorian (`sv` weeks start Monday). **Without a provider, components are English with Gregorian dates** — wrap Thai apps in `<AuraProvider locale="th">`. For dates in your own components use `useFormatDate()` — it follows the provider (`const fmt = useFormatDate(); fmt(iso, { format: 'long' })`). Plain `formatDate()` has no provider to read: since 5.0 it is English and Gregorian unless you pass `locale`, the same function from the package root and from `@jirawatpyk/aura-react/server`. Values are always Gregorian ISO dates.
103
+ `linkComponent` is used by Button `href`, Breadcrumb, Tabs and Menu items with `href`, BottomNav, Pagination `getHref` (page numbers and, since 4.17, the previous / next arrows), Stat and StatusTile `href`, Th `sortHref`, toast action `href`, SideNav and DataTable row/pager links. Each of them also takes its own `linkComponent`, which wins over the provider's. Pass `getHref` from a client component (it's a function too). The Next.js starter does all of this, and CI clicks every AURA link in it to check none triggers a full page load. Only `th` shows Buddhist-era years; `en` and `sv` are Gregorian (`sv` weeks start Monday). **Without a provider, components are English with Gregorian dates** — wrap Thai apps in `<AuraProvider locale="th">`. For dates in your own components use `useFormatDate()` — it follows the provider (`const fmt = useFormatDate(); fmt(iso, { format: 'long' })`). Plain `formatDate()` has no provider to read: since 5.0 it is English and Gregorian unless you pass `locale`, the same function from the package root and from `@jirawatpyk/aura-react/server`. Values are always Gregorian ISO dates.
104
+
105
+ TanStack Start / TanStack Router (5.80): AURA gives `linkComponent` a plain `href`, so pass a small `<a>` that hands same-origin clicks to the router — not TanStack's `<Link>`, which adds `aria-current="page"` to every link its own matcher calls active (on `/members?page=3`, the page 1 link and the "All" tab as well), over AURA's own:
106
+
107
+ ```tsx
108
+ // src/components/aura-link.tsx
109
+ import * as React from 'react';
110
+ import { useRouter } from '@tanstack/react-router';
111
+
112
+ function routerHref(href?: string): string | null {
113
+ if (href == null || typeof window === 'undefined') return null;
114
+ let url: URL;
115
+ try {
116
+ url = new URL(href, window.location.href); // also '?page=2' and 'members'
117
+ } catch {
118
+ return null;
119
+ }
120
+ const here = window.location;
121
+ if (url.origin !== here.origin) return null; // other sites, mailto:, tel:
122
+ if (url.hash && url.pathname === here.pathname && url.search === here.search) return null; // #fragment here
123
+ return url.pathname + url.search + url.hash;
124
+ }
125
+
126
+ export const AuraLink = React.forwardRef<HTMLAnchorElement, React.AnchorHTMLAttributes<HTMLAnchorElement>>(
127
+ function AuraLink({ href, onClick, onMouseEnter, onFocus, ...rest }, ref) {
128
+ const router = useRouter();
129
+ const preload = () => {
130
+ const to = routerHref(href);
131
+ if (to) router.preloadRoute({ href: to } as Parameters<typeof router.preloadRoute>[0]).catch(() => undefined);
132
+ };
133
+ return (
134
+ <a ref={ref} href={href} {...rest}
135
+ onClick={(e) => {
136
+ onClick?.(e); // AURA's own handler first (a DataTable pager with onPageChange, a toast action)
137
+ if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
138
+ const target = e.currentTarget.getAttribute('target');
139
+ if ((target && target !== '_self') || e.currentTarget.hasAttribute('download')) return;
140
+ const to = routerHref(href);
141
+ if (!to) return;
142
+ e.preventDefault();
143
+ void router.navigate({ href: to });
144
+ }}
145
+ onMouseEnter={(e) => { onMouseEnter?.(e); preload(); }}
146
+ onFocus={(e) => { onFocus?.(e); preload(); }}
147
+ />
148
+ );
149
+ },
150
+ );
151
+
152
+ // src/routes/__root.tsx — the provider inside the router (the root route's component), the document in shellComponent
153
+ export const Route = createRootRoute({
154
+ head: () => ({ links: [{ rel: 'stylesheet', href: tokensCss }, { rel: 'stylesheet', href: auraCss }] }), // '…css?url' imports
155
+ shellComponent: RootDocument, // <html><head><HeadContent /><ColorSchemeScript /></head><body>{children}<Scripts /></body></html>
156
+ component: () => (
157
+ <AuraProvider locale="th" strings={th} linkComponent={AuraLink}>
158
+ <Outlet />
159
+ <Toaster />
160
+ </AuraProvider>
161
+ ),
162
+ });
163
+ ```
164
+
165
+ Write hrefs as URLs (`getHref={(p) => '/members?page=' + p}`) and read them back with the route's `validateSearch`. A client navigation passes the query through the router's search parser and serializer, so keep params single-valued (`?a=1&a=2` comes back as JSON, `?q=1e3` as `1000`) or set the router's `parseSearch` / `stringifySearch`. With a `basepath`, write hrefs with it; a same-origin href outside it goes to the router's not-found route. DataTable keyboard paging (PageDown / PageUp, arrows past the page) clicks the pager links, and since 5.80 the focus moves to the new page's row. The TanStack Start starter (`templates/tanstack-start-starter`) has all of this; CI builds it against the packed packages and clicks every AURA link in it — Button, Breadcrumb, link Tabs, DropdownMenu items, BottomNav, Pagination, Th `sortHref`, Stat, StatusTile, SideNav, DataTable rows and pager, toast actions — checking that none loads a full page, that `aria-current` is AURA's and that Ctrl/⌘-click still opens a tab.
104
166
 
105
167
  ## Compact density
106
168
 
@@ -186,6 +248,10 @@ DataTable with sort and page in the URL (4.17): `onStateChange={({ sort, page })
186
248
  - **Time zone**: `timeZone` on AuraProvider, DatePicker, DateRangePicker and Calendar decides "today" (the marker, `min`/`max="today"`, the first month shown); or pass `today` as an ISO date. `todayIn('Asia/Bangkok')` is exported from the root and `/server`.
187
249
  - **Toasts** queue past three instead of dropping: six in a row all show, in order, three at a time.
188
250
 
251
+ ## 5.81 — DxT Monitor 24 (the tab bar hides after a tap)
252
+
253
+ - **BottomNav `hideOnScroll` and AppShell `headerHideOnScroll` after a tap**: with client-side navigation the bar stays mounted, and on Chrome / Android a tap leaves focus on the tapped tab — the bar then never hid on the next page. Focus in the bar now holds it only when it came from the keyboard: focus that enters right after a pointer press doesn't count, and focus moved with the keyboard does (Tab into or within the bar brings it back and keeps it; keys that only scroll the page, such as Space or PageDown, don't). A text field in the bar holds it however it was focused. No new prop; drop any wrapper that blurs the tab on click.
254
+
189
255
  ## 5.79 — visual audit, set 3 (component by component)
190
256
 
191
257
  - **One disabled look for every Button variant:** a faint fill (`--aura-button-disabled-bg`: zinc-100 light, none in dark), a faint edge (`--aura-button-disabled-border` — the variant's own border, or 1px inside for primary and danger) and the tertiary label, at full opacity — primary, secondary, danger, creative and `aria-disabled` alike; a ghost button has neither fill nor edge. (5.30 gave filled buttons a grey fill that read as a To Do pill and faded outline ones.)
@@ -498,7 +564,7 @@ What Chamber-OS's own page header did that PageHeader didn't, so it can be rebui
498
564
 
499
565
  - **Breadcrumb touch targets** (request 11): on a touch screen (`pointer: coarse`) every crumb link or button (and "…") gets a hit area 44px tall and as wide as its text plus 24px; the text doesn't move. It reaches 12px into the gutter on each side, so short neighbours never share a spot, and only 4px below the text, so it stops short of a PageHeader title right underneath — the rest of the height is above, so keep about 20px clear above a breadcrumb (AppShell's content padding already is). When the crumbs wrap, the rows sit 24px apart on touch screens so one row never covers the next. The current page (not a link) gets none; a link component must render its `<a>` directly in the crumb's `li`. Apps that added their own `.aura-crumbs a::after` rule should drop it.
500
566
  - **StatusTile `status="unknown"`** (request 12): for a thing with no data yet (a site before its first check) — a dashed circle (circle-dot-dashed) in `aura-fg-tertiary` on the plain surface and border, status word "No data" (th: ยังไม่มีข้อมูล, sv: Ingen data), value in the text colour. An unrecognised `status` now shows as unknown rather than OK, with a dev warning.
501
- - **AppShell `headerHideOnScroll`** (request 13): below 1024px the top bar slides up out of view while the page scrolls down (after it has moved past the bar's height and kept going 8px) and comes back after 8px up, near the top, and whenever focus enters it. `--aura-shell-bar-height` is 0 while it is away, so a sticky table header pinned under the bar moves up with it. No slide with reduced motion. The page must scroll the window (AppShell's does).
567
+ - **AppShell `headerHideOnScroll`** (request 13): below 1024px the top bar slides up out of view while the page scrolls down (after it has moved past the bar's height and kept going 8px) and comes back after 8px up, near the top, and whenever focus enters it (5.81: a tap's focus doesn't keep it there; keyboard focus does). `--aura-shell-bar-height` is 0 while it is away, so a sticky table header pinned under the bar moves up with it. No slide with reduced motion. The page must scroll the window (AppShell's does).
502
568
  - **BottomNav `hideOnScroll`** (request 14): the same for the tab bar, sliding down. Its spacer stays (nothing shifts) and `--aura-bottomnav-offset` drops while it is away, so viewport ActionBars and toasts follow it down. **ActionBar `hidesBottomNav`**: while that bar is on the page the BottomNav and its spacer are gone and the bar sits on the bottom edge — for a page whose own actions (Acknowledge, Visit site) replace the tabs. Any element can do the same with `data-aura-hide-bottomnav`.
503
569
 
504
570
  ## 5.34 — StatusTile, TileGrid, Sparkline (DxT Monitor Overview S06)
@@ -6548,6 +6548,7 @@ window.Aura = (() => {
6548
6548
  if (next === page) return false;
6549
6549
  if (!quiet) emit(sort, next);
6550
6550
  if (linkPaging) {
6551
+ if (focus) pending.current = { ...focus, page: next, at: Date.now() };
6551
6552
  const a = next === page - 1 ? prevLink.current : next === page + 1 ? nextLink.current : null;
6552
6553
  if (a) a.click();
6553
6554
  else jumpState[1](next);
@@ -6646,6 +6647,11 @@ window.Aura = (() => {
6646
6647
  React34.useEffect(function() {
6647
6648
  const p = pending.current;
6648
6649
  if (!p || !gridRef.current) return;
6650
+ if (p.at != null && Date.now() - p.at > 1e4) {
6651
+ pending.current = null;
6652
+ return;
6653
+ }
6654
+ if (p.page != null && p.page !== page) return;
6649
6655
  let c = p.c;
6650
6656
  if (p.key) {
6651
6657
  let at = -1;
@@ -8745,8 +8751,10 @@ window.Aura = (() => {
8745
8751
  // src/scrollHide.ts
8746
8752
  var React48 = __toESM(require_react(), 1);
8747
8753
  var STEP = 8;
8754
+ var TEXT_ENTRY = 'textarea, select, [contenteditable=""], [contenteditable="true"], [contenteditable="plaintext-only"], input:not([type]), input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="tel"], input[type="number"], input[type="password"]';
8748
8755
  function useHideOnScroll(enabled, el, media, from) {
8749
8756
  const st = React48.useState(false), hidden = st[0], setHidden = st[1];
8757
+ const tap = React48.useRef({ pressedAt: -1e9, focus: false });
8750
8758
  React48.useEffect(
8751
8759
  function() {
8752
8760
  if (!enabled || typeof window === "undefined") {
@@ -8756,6 +8764,11 @@ window.Aura = (() => {
8756
8764
  const mq = window.matchMedia ? window.matchMedia(media) : null;
8757
8765
  let last = window.scrollY, lastH = window.innerHeight, resizedAt = -1e9, run = 0;
8758
8766
  let restore = null;
8767
+ function holdsBar(node2) {
8768
+ const a = document.activeElement;
8769
+ if (!node2 || !a || !node2.contains(a)) return false;
8770
+ return !tap.current.focus || a.matches(TEXT_ENTRY);
8771
+ }
8759
8772
  function update() {
8760
8773
  if (restore) {
8761
8774
  const r2 = restore;
@@ -8770,7 +8783,7 @@ window.Aura = (() => {
8770
8783
  const y = window.scrollY, dy = y - last;
8771
8784
  last = y;
8772
8785
  const node2 = el.current;
8773
- if (mq && !mq.matches || y <= from || node2 && document.activeElement && node2.contains(document.activeElement)) {
8786
+ if (mq && !mq.matches || y <= from || holdsBar(node2)) {
8774
8787
  run = 0;
8775
8788
  setHidden(false);
8776
8789
  return;
@@ -8790,7 +8803,12 @@ window.Aura = (() => {
8790
8803
  function disarm() {
8791
8804
  restore = null;
8792
8805
  }
8806
+ function onPress(e) {
8807
+ restore = null;
8808
+ tap.current.pressedAt = e.timeStamp;
8809
+ }
8793
8810
  function onKey(e) {
8811
+ tap.current.pressedAt = -1e9;
8794
8812
  if (e.key !== "Tab" && e.code !== "Tab") {
8795
8813
  if (e.key !== "Shift") restore = null;
8796
8814
  return;
@@ -8800,7 +8818,8 @@ window.Aura = (() => {
8800
8818
  tabbing = false;
8801
8819
  });
8802
8820
  }
8803
- function reveal() {
8821
+ function reveal(e) {
8822
+ tap.current.focus = e.timeStamp - tap.current.pressedAt < 1e3;
8804
8823
  const n3 = el.current;
8805
8824
  if (n3 && n3.classList.contains("is-away")) {
8806
8825
  n3.classList.remove("is-away");
@@ -8816,7 +8835,7 @@ window.Aura = (() => {
8816
8835
  document.addEventListener("keydown", onKey, true);
8817
8836
  window.addEventListener("wheel", disarm, { passive: true, capture: true });
8818
8837
  window.addEventListener("touchstart", disarm, { passive: true, capture: true });
8819
- window.addEventListener("pointerdown", disarm, true);
8838
+ window.addEventListener("pointerdown", onPress, true);
8820
8839
  const node = el.current;
8821
8840
  if (node) node.addEventListener("focusin", reveal);
8822
8841
  if (mq && mq.addEventListener) mq.addEventListener("change", update);
@@ -8826,7 +8845,7 @@ window.Aura = (() => {
8826
8845
  document.removeEventListener("keydown", onKey, true);
8827
8846
  window.removeEventListener("wheel", disarm, true);
8828
8847
  window.removeEventListener("touchstart", disarm, true);
8829
- window.removeEventListener("pointerdown", disarm, true);
8848
+ window.removeEventListener("pointerdown", onPress, true);
8830
8849
  if (node) node.removeEventListener("focusin", reveal);
8831
8850
  if (mq && mq.removeEventListener) mq.removeEventListener("change", update);
8832
8851
  };
@@ -6530,6 +6530,7 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
6530
6530
  if (next === page) return false;
6531
6531
  if (!quiet) emit(sort, next);
6532
6532
  if (linkPaging) {
6533
+ if (focus) pending.current = { ...focus, page: next, at: Date.now() };
6533
6534
  const a = next === page - 1 ? prevLink.current : next === page + 1 ? nextLink.current : null;
6534
6535
  if (a) a.click();
6535
6536
  else jumpState[1](next);
@@ -6628,6 +6629,11 @@ var DataTableImpl = React34.forwardRef(function DataTable(props, ref) {
6628
6629
  React34.useEffect(function() {
6629
6630
  const p = pending.current;
6630
6631
  if (!p || !gridRef.current) return;
6632
+ if (p.at != null && Date.now() - p.at > 1e4) {
6633
+ pending.current = null;
6634
+ return;
6635
+ }
6636
+ if (p.page != null && p.page !== page) return;
6631
6637
  let c = p.c;
6632
6638
  if (p.key) {
6633
6639
  let at = -1;
@@ -8727,8 +8733,10 @@ var React49 = __toESM(require("react"), 1);
8727
8733
  // src/scrollHide.ts
8728
8734
  var React48 = __toESM(require("react"), 1);
8729
8735
  var STEP = 8;
8736
+ var TEXT_ENTRY = 'textarea, select, [contenteditable=""], [contenteditable="true"], [contenteditable="plaintext-only"], input:not([type]), input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="tel"], input[type="number"], input[type="password"]';
8730
8737
  function useHideOnScroll(enabled, el, media, from) {
8731
8738
  const st = React48.useState(false), hidden = st[0], setHidden = st[1];
8739
+ const tap = React48.useRef({ pressedAt: -1e9, focus: false });
8732
8740
  React48.useEffect(
8733
8741
  function() {
8734
8742
  if (!enabled || typeof window === "undefined") {
@@ -8738,6 +8746,11 @@ function useHideOnScroll(enabled, el, media, from) {
8738
8746
  const mq = window.matchMedia ? window.matchMedia(media) : null;
8739
8747
  let last = window.scrollY, lastH = window.innerHeight, resizedAt = -1e9, run = 0;
8740
8748
  let restore = null;
8749
+ function holdsBar(node2) {
8750
+ const a = document.activeElement;
8751
+ if (!node2 || !a || !node2.contains(a)) return false;
8752
+ return !tap.current.focus || a.matches(TEXT_ENTRY);
8753
+ }
8741
8754
  function update() {
8742
8755
  if (restore) {
8743
8756
  const r2 = restore;
@@ -8752,7 +8765,7 @@ function useHideOnScroll(enabled, el, media, from) {
8752
8765
  const y = window.scrollY, dy = y - last;
8753
8766
  last = y;
8754
8767
  const node2 = el.current;
8755
- if (mq && !mq.matches || y <= from || node2 && document.activeElement && node2.contains(document.activeElement)) {
8768
+ if (mq && !mq.matches || y <= from || holdsBar(node2)) {
8756
8769
  run = 0;
8757
8770
  setHidden(false);
8758
8771
  return;
@@ -8772,7 +8785,12 @@ function useHideOnScroll(enabled, el, media, from) {
8772
8785
  function disarm() {
8773
8786
  restore = null;
8774
8787
  }
8788
+ function onPress(e) {
8789
+ restore = null;
8790
+ tap.current.pressedAt = e.timeStamp;
8791
+ }
8775
8792
  function onKey(e) {
8793
+ tap.current.pressedAt = -1e9;
8776
8794
  if (e.key !== "Tab" && e.code !== "Tab") {
8777
8795
  if (e.key !== "Shift") restore = null;
8778
8796
  return;
@@ -8782,7 +8800,8 @@ function useHideOnScroll(enabled, el, media, from) {
8782
8800
  tabbing = false;
8783
8801
  });
8784
8802
  }
8785
- function reveal() {
8803
+ function reveal(e) {
8804
+ tap.current.focus = e.timeStamp - tap.current.pressedAt < 1e3;
8786
8805
  const n3 = el.current;
8787
8806
  if (n3 && n3.classList.contains("is-away")) {
8788
8807
  n3.classList.remove("is-away");
@@ -8798,7 +8817,7 @@ function useHideOnScroll(enabled, el, media, from) {
8798
8817
  document.addEventListener("keydown", onKey, true);
8799
8818
  window.addEventListener("wheel", disarm, { passive: true, capture: true });
8800
8819
  window.addEventListener("touchstart", disarm, { passive: true, capture: true });
8801
- window.addEventListener("pointerdown", disarm, true);
8820
+ window.addEventListener("pointerdown", onPress, true);
8802
8821
  const node = el.current;
8803
8822
  if (node) node.addEventListener("focusin", reveal);
8804
8823
  if (mq && mq.addEventListener) mq.addEventListener("change", update);
@@ -8808,7 +8827,7 @@ function useHideOnScroll(enabled, el, media, from) {
8808
8827
  document.removeEventListener("keydown", onKey, true);
8809
8828
  window.removeEventListener("wheel", disarm, true);
8810
8829
  window.removeEventListener("touchstart", disarm, true);
8811
- window.removeEventListener("pointerdown", disarm, true);
8830
+ window.removeEventListener("pointerdown", onPress, true);
8812
8831
  if (node) node.removeEventListener("focusin", reveal);
8813
8832
  if (mq && mq.removeEventListener) mq.removeEventListener("change", update);
8814
8833
  };
@@ -487,6 +487,7 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
487
487
  if (next === page) return false;
488
488
  if (!quiet) emit(sort, next);
489
489
  if (linkPaging) {
490
+ if (focus) pending.current = { ...focus, page: next, at: Date.now() };
490
491
  const a = next === page - 1 ? prevLink.current : next === page + 1 ? nextLink.current : null;
491
492
  if (a) a.click();
492
493
  else jumpState[1](next);
@@ -585,6 +586,11 @@ const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
585
586
  React.useEffect(function() {
586
587
  const p = pending.current;
587
588
  if (!p || !gridRef.current) return;
589
+ if (p.at != null && Date.now() - p.at > 1e4) {
590
+ pending.current = null;
591
+ return;
592
+ }
593
+ if (p.page != null && p.page !== page) return;
588
594
  let c = p.c;
589
595
  if (p.key) {
590
596
  let at = -1;
@@ -1,8 +1,10 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  const STEP = 8;
4
+ const TEXT_ENTRY = 'textarea, select, [contenteditable=""], [contenteditable="true"], [contenteditable="plaintext-only"], input:not([type]), input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="tel"], input[type="number"], input[type="password"]';
4
5
  function useHideOnScroll(enabled, el, media, from) {
5
6
  const st = React.useState(false), hidden = st[0], setHidden = st[1];
7
+ const tap = React.useRef({ pressedAt: -1e9, focus: false });
6
8
  React.useEffect(
7
9
  function() {
8
10
  if (!enabled || typeof window === "undefined") {
@@ -12,6 +14,11 @@ function useHideOnScroll(enabled, el, media, from) {
12
14
  const mq = window.matchMedia ? window.matchMedia(media) : null;
13
15
  let last = window.scrollY, lastH = window.innerHeight, resizedAt = -1e9, run = 0;
14
16
  let restore = null;
17
+ function holdsBar(node2) {
18
+ const a = document.activeElement;
19
+ if (!node2 || !a || !node2.contains(a)) return false;
20
+ return !tap.current.focus || a.matches(TEXT_ENTRY);
21
+ }
15
22
  function update() {
16
23
  if (restore) {
17
24
  const r = restore;
@@ -26,7 +33,7 @@ function useHideOnScroll(enabled, el, media, from) {
26
33
  const y = window.scrollY, dy = y - last;
27
34
  last = y;
28
35
  const node2 = el.current;
29
- if (mq && !mq.matches || y <= from || node2 && document.activeElement && node2.contains(document.activeElement)) {
36
+ if (mq && !mq.matches || y <= from || holdsBar(node2)) {
30
37
  run = 0;
31
38
  setHidden(false);
32
39
  return;
@@ -46,7 +53,12 @@ function useHideOnScroll(enabled, el, media, from) {
46
53
  function disarm() {
47
54
  restore = null;
48
55
  }
56
+ function onPress(e) {
57
+ restore = null;
58
+ tap.current.pressedAt = e.timeStamp;
59
+ }
49
60
  function onKey(e) {
61
+ tap.current.pressedAt = -1e9;
50
62
  if (e.key !== "Tab" && e.code !== "Tab") {
51
63
  if (e.key !== "Shift") restore = null;
52
64
  return;
@@ -56,7 +68,8 @@ function useHideOnScroll(enabled, el, media, from) {
56
68
  tabbing = false;
57
69
  });
58
70
  }
59
- function reveal() {
71
+ function reveal(e) {
72
+ tap.current.focus = e.timeStamp - tap.current.pressedAt < 1e3;
60
73
  const n = el.current;
61
74
  if (n && n.classList.contains("is-away")) {
62
75
  n.classList.remove("is-away");
@@ -72,7 +85,7 @@ function useHideOnScroll(enabled, el, media, from) {
72
85
  document.addEventListener("keydown", onKey, true);
73
86
  window.addEventListener("wheel", disarm, { passive: true, capture: true });
74
87
  window.addEventListener("touchstart", disarm, { passive: true, capture: true });
75
- window.addEventListener("pointerdown", disarm, true);
88
+ window.addEventListener("pointerdown", onPress, true);
76
89
  const node = el.current;
77
90
  if (node) node.addEventListener("focusin", reveal);
78
91
  if (mq && mq.addEventListener) mq.addEventListener("change", update);
@@ -82,7 +95,7 @@ function useHideOnScroll(enabled, el, media, from) {
82
95
  document.removeEventListener("keydown", onKey, true);
83
96
  window.removeEventListener("wheel", disarm, true);
84
97
  window.removeEventListener("touchstart", disarm, true);
85
- window.removeEventListener("pointerdown", disarm, true);
98
+ window.removeEventListener("pointerdown", onPress, true);
86
99
  if (node) node.removeEventListener("focusin", reveal);
87
100
  if (mq && mq.removeEventListener) mq.removeEventListener("change", update);
88
101
  };
package/dist/styles.css CHANGED
@@ -2153,6 +2153,11 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2153
2153
  .aura-filterbar--fill .aura-filterbar__controls > .aura-filterselect .aura-filterselect__chevron { margin-inline-start: auto; }
2154
2154
  /* Compact filters sit closer together than full fields. */
2155
2155
  .aura-filterbar__controls:has(> .aura-filterselect) { gap: var(--aura-space-2); }
2156
+ /* 5.80: on a phone 6px, so three filters and a Tag keep one 375px row with either font source — the self-hosted
2157
+ * Inter (aura-fonts.local.css) measured 2.3px over at 8px. */
2158
+ @media (max-width: 639.98px) {
2159
+ .aura-filterbar__controls:has(> .aura-filterselect) { gap: 6px; }
2160
+ }
2156
2161
  /* 5.42 (Chamber-OS 146): collapseBelow — below the breakpoint the controls fold into a "Filters" sheet; the button
2157
2162
  * shares a row with the search, at its end, as tall as the search (the count and actions wrap below). From the
2158
2163
  * breakpoint up the search and the hidden button sit in a display: contents box, so the bar lays out as before. */
@@ -2155,6 +2155,11 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2155
2155
  .aura-filterbar--fill .aura-filterbar__controls > .aura-filterselect .aura-filterselect__chevron { margin-inline-start: auto; }
2156
2156
  /* Compact filters sit closer together than full fields. */
2157
2157
  .aura-filterbar__controls:has(> .aura-filterselect) { gap: var(--aura-space-2); }
2158
+ /* 5.80: on a phone 6px, so three filters and a Tag keep one 375px row with either font source — the self-hosted
2159
+ * Inter (aura-fonts.local.css) measured 2.3px over at 8px. */
2160
+ @media (max-width: 639.98px) {
2161
+ .aura-filterbar__controls:has(> .aura-filterselect) { gap: 6px; }
2162
+ }
2158
2163
  /* 5.42 (Chamber-OS 146): collapseBelow — below the breakpoint the controls fold into a "Filters" sheet; the button
2159
2164
  * shares a row with the search, at its end, as tall as the search (the count and actions wrap below). From the
2160
2165
  * breakpoint up the search and the hidden button sit in a display: contents box, so the bar lays out as before. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-react",
3
- "version": "5.79.0",
3
+ "version": "5.81.0",
4
4
  "description": "AURA Design System — React components (Enterprise Standard, Human Creative). Thai-first: Buddhist-era dates, Thai collation.",
5
5
  "license": "MIT",
6
6
  "type": "module",