@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 +68 -2
- package/dist/aura.bundle.js +23 -4
- package/dist/cjs/index.cjs +23 -4
- package/dist/esm/DataTable.js +6 -0
- package/dist/esm/scrollHide.js +17 -4
- package/dist/styles.css +5 -0
- package/dist/styles.layer.css +5 -0
- package/package.json +1 -1
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)
|
package/dist/aura.bundle.js
CHANGED
|
@@ -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
|
|
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",
|
|
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",
|
|
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
|
};
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
|
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",
|
|
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",
|
|
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
|
};
|
package/dist/esm/DataTable.js
CHANGED
|
@@ -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;
|
package/dist/esm/scrollHide.js
CHANGED
|
@@ -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
|
|
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",
|
|
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",
|
|
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. */
|
package/dist/styles.layer.css
CHANGED
|
@@ -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.
|
|
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",
|