tempest-react-sdk 0.43.0 → 0.45.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.
Files changed (133) hide show
  1. package/README.md +12 -5
  2. package/dist/app/AppProviders.cjs +1 -1
  3. package/dist/app/AppProviders.js +6 -6
  4. package/dist/audio/sfx-pool.cjs +2 -0
  5. package/dist/audio/sfx-pool.cjs.map +1 -0
  6. package/dist/audio/sfx-pool.js +62 -0
  7. package/dist/audio/sfx-pool.js.map +1 -0
  8. package/dist/audio/use-sfx-pool.cjs +2 -0
  9. package/dist/audio/use-sfx-pool.cjs.map +1 -0
  10. package/dist/audio/use-sfx-pool.js +18 -0
  11. package/dist/audio/use-sfx-pool.js.map +1 -0
  12. package/dist/auth/create-tempest-auth.cjs +1 -1
  13. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  14. package/dist/auth/create-tempest-auth.js +47 -44
  15. package/dist/auth/create-tempest-auth.js.map +1 -1
  16. package/dist/auth/lazy-with-retry.cjs +1 -1
  17. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  18. package/dist/auth/lazy-with-retry.js +8 -1
  19. package/dist/auth/lazy-with-retry.js.map +1 -1
  20. package/dist/components/AppBar/AppBar.cjs +1 -1
  21. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  22. package/dist/components/AppBar/AppBar.js +27 -25
  23. package/dist/components/AppBar/AppBar.js.map +1 -1
  24. package/dist/components/AppBar/use-sticky-body-warning.cjs +2 -0
  25. package/dist/components/AppBar/use-sticky-body-warning.cjs.map +1 -0
  26. package/dist/components/AppBar/use-sticky-body-warning.js +16 -0
  27. package/dist/components/AppBar/use-sticky-body-warning.js.map +1 -0
  28. package/dist/components/BarList/BarList.cjs +2 -0
  29. package/dist/components/BarList/BarList.cjs.map +1 -0
  30. package/dist/components/BarList/BarList.js +42 -0
  31. package/dist/components/BarList/BarList.js.map +1 -0
  32. package/dist/components/BarList/BarList.module.cjs +2 -0
  33. package/dist/components/BarList/BarList.module.cjs.map +1 -0
  34. package/dist/components/BarList/BarList.module.js +15 -0
  35. package/dist/components/BarList/BarList.module.js.map +1 -0
  36. package/dist/components/BarList/bar-list-model.cjs +2 -0
  37. package/dist/components/BarList/bar-list-model.cjs.map +1 -0
  38. package/dist/components/BarList/bar-list-model.js +18 -0
  39. package/dist/components/BarList/bar-list-model.js.map +1 -0
  40. package/dist/components/DataTable/DataTable.cjs +1 -1
  41. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  42. package/dist/components/DataTable/DataTable.js +141 -108
  43. package/dist/components/DataTable/DataTable.js.map +1 -1
  44. package/dist/components/DataTable/DataTable.module.cjs +1 -1
  45. package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
  46. package/dist/components/DataTable/DataTable.module.js +13 -10
  47. package/dist/components/DataTable/DataTable.module.js.map +1 -1
  48. package/dist/components/DataTable/LoadingRows.cjs +2 -0
  49. package/dist/components/DataTable/LoadingRows.cjs.map +1 -0
  50. package/dist/components/DataTable/LoadingRows.js +18 -0
  51. package/dist/components/DataTable/LoadingRows.js.map +1 -0
  52. package/dist/components/DataTable/use-dev-warnings.cjs +2 -0
  53. package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -0
  54. package/dist/components/DataTable/use-dev-warnings.js +17 -0
  55. package/dist/components/DataTable/use-dev-warnings.js.map +1 -0
  56. package/dist/components/FilterBar/filter-apply.cjs +2 -0
  57. package/dist/components/FilterBar/filter-apply.cjs.map +1 -0
  58. package/dist/components/FilterBar/filter-apply.js +76 -0
  59. package/dist/components/FilterBar/filter-apply.js.map +1 -0
  60. package/dist/components/FilterBar/filter-query.cjs +2 -0
  61. package/dist/components/FilterBar/filter-query.cjs.map +1 -0
  62. package/dist/components/FilterBar/filter-query.js +38 -0
  63. package/dist/components/FilterBar/filter-query.js.map +1 -0
  64. package/dist/hooks/use-countdown.cjs +2 -0
  65. package/dist/hooks/use-countdown.cjs.map +1 -0
  66. package/dist/hooks/use-countdown.js +18 -0
  67. package/dist/hooks/use-countdown.js.map +1 -0
  68. package/dist/hooks/use-latest-ref.cjs +2 -0
  69. package/dist/hooks/use-latest-ref.cjs.map +1 -0
  70. package/dist/hooks/use-latest-ref.js +10 -0
  71. package/dist/hooks/use-latest-ref.js.map +1 -0
  72. package/dist/hooks/use-typewriter.cjs +2 -0
  73. package/dist/hooks/use-typewriter.cjs.map +1 -0
  74. package/dist/hooks/use-typewriter.js +28 -0
  75. package/dist/hooks/use-typewriter.js.map +1 -0
  76. package/dist/http/api-client.cjs +1 -1
  77. package/dist/http/api-client.cjs.map +1 -1
  78. package/dist/http/api-client.js +42 -43
  79. package/dist/http/api-client.js.map +1 -1
  80. package/dist/http/build-url.cjs +2 -0
  81. package/dist/http/build-url.cjs.map +1 -0
  82. package/dist/http/build-url.js +26 -0
  83. package/dist/http/build-url.js.map +1 -0
  84. package/dist/http/describe-api-error.cjs +2 -0
  85. package/dist/http/describe-api-error.cjs.map +1 -0
  86. package/dist/http/describe-api-error.js +22 -0
  87. package/dist/http/describe-api-error.js.map +1 -0
  88. package/dist/http/use-describe-api-error.cjs +2 -0
  89. package/dist/http/use-describe-api-error.cjs.map +1 -0
  90. package/dist/http/use-describe-api-error.js +15 -0
  91. package/dist/http/use-describe-api-error.js.map +1 -0
  92. package/dist/i18n/I18nProvider.cjs +1 -1
  93. package/dist/i18n/I18nProvider.cjs.map +1 -1
  94. package/dist/i18n/I18nProvider.js +4 -1
  95. package/dist/i18n/I18nProvider.js.map +1 -1
  96. package/dist/query/QueryProvider.cjs +1 -1
  97. package/dist/query/QueryProvider.cjs.map +1 -1
  98. package/dist/query/QueryProvider.js +13 -12
  99. package/dist/query/QueryProvider.js.map +1 -1
  100. package/dist/query/retry-policy.cjs +2 -0
  101. package/dist/query/retry-policy.cjs.map +1 -0
  102. package/dist/query/retry-policy.js +10 -0
  103. package/dist/query/retry-policy.js.map +1 -0
  104. package/dist/styles.css +1 -1
  105. package/dist/tempest-react-sdk.cjs +1 -1
  106. package/dist/tempest-react-sdk.d.ts +867 -10
  107. package/dist/tempest-react-sdk.js +276 -261
  108. package/dist/utils/compressed-storage.cjs +2 -0
  109. package/dist/utils/compressed-storage.cjs.map +1 -0
  110. package/dist/utils/compressed-storage.js +52 -0
  111. package/dist/utils/compressed-storage.js.map +1 -0
  112. package/dist/utils/csv.cjs +4 -0
  113. package/dist/utils/csv.cjs.map +1 -0
  114. package/dist/utils/csv.js +25 -0
  115. package/dist/utils/csv.js.map +1 -0
  116. package/dist/utils/format.cjs +1 -1
  117. package/dist/utils/format.cjs.map +1 -1
  118. package/dist/utils/format.js +11 -4
  119. package/dist/utils/format.js.map +1 -1
  120. package/dist/utils/numbers.cjs +1 -1
  121. package/dist/utils/numbers.cjs.map +1 -1
  122. package/dist/utils/numbers.js +5 -2
  123. package/dist/utils/numbers.js.map +1 -1
  124. package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
  125. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  126. package/dist/vite/tempest-pwa-dev-sw.js +10 -5
  127. package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
  128. package/dist/vite/tempest-pwa-manifest.cjs +2 -2
  129. package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
  130. package/dist/vite/tempest-pwa-manifest.js +17 -13
  131. package/dist/vite/tempest-pwa-manifest.js.map +1 -1
  132. package/dist/vite.d.ts +19 -2
  133. package/package.json +1 -1
@@ -10,7 +10,14 @@ function t(t, n = {}) {
10
10
  return await new Promise((t) => setTimeout(t, i * 2 ** (e - 1))), o(e + 1);
11
11
  }
12
12
  }
13
- return e(o);
13
+ let s = null;
14
+ function c() {
15
+ return s || (s = o(), s.catch(() => {
16
+ s = null;
17
+ })), s;
18
+ }
19
+ let l = e(c);
20
+ return l.preload = c, l;
14
21
  }
15
22
  //#endregion
16
23
  export { t as lazyWithRetry };
@@ -1 +1 @@
1
- {"version":3,"file":"lazy-with-retry.js","names":[],"sources":["../../src/auth/lazy-with-retry.ts"],"sourcesContent":["import { lazy } from \"react\";\nimport type { ComponentType } from \"react\";\n\nexport interface LazyWithRetryOptions {\n /** Max attempts. Default: 3. */\n retries?: number;\n /** Initial delay (ms) before retrying. Default: 400. */\n initialDelay?: number;\n /**\n * Reload the page after every retry fails. Helps when the stale chunk\n * error is caused by an old `index.html` referencing deleted bundles.\n * Default: true.\n */\n reloadOnFinalFailure?: boolean;\n}\n\n/**\n * Wrap `React.lazy` with automatic retry. Common cause of failure:\n * deployed-then-cached `index.html` references chunk filenames that no\n * longer exist. Retrying after a short delay typically picks up the new\n * bundle; a final `location.reload()` recovers from stale `index.html`.\n *\n * @example\n * const Settings = lazyWithRetry(() => import(\"./Settings\"));\n */\nexport function lazyWithRetry<T extends ComponentType<unknown>>(\n factory: () => Promise<{ default: T }>,\n options: LazyWithRetryOptions = {},\n): ReturnType<typeof lazy<T>> {\n const { retries = 3, initialDelay = 400, reloadOnFinalFailure = true } = options;\n\n async function load(attempt = 1): Promise<{ default: T }> {\n try {\n return await factory();\n } catch (error) {\n if (attempt >= retries) {\n if (reloadOnFinalFailure && typeof window !== \"undefined\") {\n window.location.reload();\n }\n throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, initialDelay * 2 ** (attempt - 1)));\n return load(attempt + 1);\n }\n }\n\n return lazy(load) as ReturnType<typeof lazy<T>>;\n}\n"],"mappings":";;AAyBA,SAAgB,EACZ,GACA,IAAgC,CAAC,GACP;CAC1B,IAAM,EAAE,aAAU,GAAG,kBAAe,KAAK,0BAAuB,OAAS;CAEzE,eAAe,EAAK,IAAU,GAA4B;EACtD,IAAI;GACA,OAAO,MAAM,EAAQ;EACzB,SAAS,GAAO;GACZ,IAAI,KAAW,GAIX,MAHI,KAAwB,OAAO,SAAW,OAC1C,OAAO,SAAS,OAAO,GAErB;GAGV,OADA,MAAM,IAAI,SAAS,MAAY,WAAW,GAAS,IAAe,MAAM,IAAU,EAAE,CAAC,GAC9E,EAAK,IAAU,CAAC;EAC3B;CACJ;CAEA,OAAO,EAAK,CAAI;AACpB"}
1
+ {"version":3,"file":"lazy-with-retry.js","names":[],"sources":["../../src/auth/lazy-with-retry.ts"],"sourcesContent":["import { lazy } from \"react\";\nimport type { ComponentType } from \"react\";\n\nexport interface LazyWithRetryOptions {\n /** Max attempts. Default: 3. */\n retries?: number;\n /** Initial delay (ms) before retrying. Default: 400. */\n initialDelay?: number;\n /**\n * Reload the page after every retry fails. Helps when the stale chunk\n * error is caused by an old `index.html` referencing deleted bundles.\n * Default: true.\n */\n reloadOnFinalFailure?: boolean;\n}\n\n/** A lazy component that can also be fetched ahead of being rendered. */\nexport type PreloadableLazy<T extends ComponentType<unknown>> = ReturnType<typeof lazy<T>> & {\n /**\n * Start fetching the chunk now, before anything renders it.\n *\n * Call it on the interaction that makes the route likely — hovering the\n * link, opening the menu that holds it, finishing the step before it — so\n * the chunk is warm by the time the user commits and the suspense fallback\n * never appears.\n *\n * Shares its work with the render path: whichever fires first performs the\n * single fetch and the other awaits the same promise. Safe to call\n * repeatedly.\n *\n * @returns The module, once loaded. Rejects when every retry failed; that\n * rejection is already handled internally, so a fire-and-forget call\n * never surfaces as an unhandled rejection.\n */\n preload: () => Promise<{ default: T }>;\n};\n\n/**\n * Wrap `React.lazy` with automatic retry and a `preload()` method.\n *\n * Common cause of failure: deployed-then-cached `index.html` references chunk\n * filenames that no longer exist. Retrying after a short delay typically picks\n * up the new bundle; a final `location.reload()` recovers from stale\n * `index.html`.\n *\n * @example\n * const Settings = lazyWithRetry(() => import(\"./Settings\"));\n *\n * // Warm the chunk when the route becomes likely, not when it is needed.\n * <a href=\"/settings\" onMouseEnter={() => void Settings.preload()}>Settings</a>\n */\nexport function lazyWithRetry<T extends ComponentType<unknown>>(\n factory: () => Promise<{ default: T }>,\n options: LazyWithRetryOptions = {},\n): PreloadableLazy<T> {\n const { retries = 3, initialDelay = 400, reloadOnFinalFailure = true } = options;\n\n async function load(attempt = 1): Promise<{ default: T }> {\n try {\n return await factory();\n } catch (error) {\n if (attempt >= retries) {\n if (reloadOnFinalFailure && typeof window !== \"undefined\") {\n window.location.reload();\n }\n throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, initialDelay * 2 ** (attempt - 1)));\n return load(attempt + 1);\n }\n }\n\n let pending: Promise<{ default: T }> | null = null;\n\n /**\n * Run `load` at most once at a time, so preloading and rendering never\n * fetch the same chunk twice.\n *\n * The internal `catch` does two jobs: it clears the memo, so a component\n * that failed can be tried again once an error boundary resets, and it\n * marks the promise as handled, so a `preload()` nobody awaited does not\n * raise an unhandled rejection. Callers still get a promise that rejects.\n */\n function loadOnce(): Promise<{ default: T }> {\n if (!pending) {\n pending = load();\n pending.catch(() => {\n pending = null;\n });\n }\n return pending;\n }\n\n const component = lazy(loadOnce) as PreloadableLazy<T>;\n component.preload = loadOnce;\n return component;\n}\n"],"mappings":";;AAmDA,SAAgB,EACZ,GACA,IAAgC,CAAC,GACf;CAClB,IAAM,EAAE,aAAU,GAAG,kBAAe,KAAK,0BAAuB,OAAS;CAEzE,eAAe,EAAK,IAAU,GAA4B;EACtD,IAAI;GACA,OAAO,MAAM,EAAQ;EACzB,SAAS,GAAO;GACZ,IAAI,KAAW,GAIX,MAHI,KAAwB,OAAO,SAAW,OAC1C,OAAO,SAAS,OAAO,GAErB;GAGV,OADA,MAAM,IAAI,SAAS,MAAY,WAAW,GAAS,IAAe,MAAM,IAAU,EAAE,CAAC,GAC9E,EAAK,IAAU,CAAC;EAC3B;CACJ;CAEA,IAAI,IAA0C;CAW9C,SAAS,IAAoC;EAOzC,OANK,MACD,IAAU,EAAK,GACf,EAAQ,YAAY;GAChB,IAAU;EACd,CAAC,IAEE;CACX;CAEA,IAAM,IAAY,EAAK,CAAQ;CAE/B,OADA,EAAU,UAAU,GACb;AACX"}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("./AppBar.module.cjs");let n=require("react/jsx-runtime"),r=require("lucide-react");function i({title:i,leading:a,showBack:o=!1,onBack:s,backLabel:c=`Go back`,backIcon:l,brand:u,actions:d,centered:f=!1,sticky:p=!0,tone:m=`surface`,bordered:h=!0,safeArea:g=!0,className:_,...v}){let y=s??(()=>window.history.back()),b=a??(0,n.jsxs)(n.Fragment,{children:[o&&(0,n.jsx)(`button`,{type:`button`,className:t.default.iconBtn,onClick:y,"aria-label":c,children:l??(0,n.jsx)(r.ArrowLeft,{size:22,"aria-hidden":`true`})}),u&&(0,n.jsx)(`span`,{className:t.default.brand,children:u})]});return(0,n.jsxs)(`header`,{className:e.cn(t.default.bar,t.default[m],f&&t.default.centered,p&&t.default.sticky,h&&t.default.bordered,g&&t.default.safeArea,_),...v,children:[(0,n.jsx)(`div`,{className:t.default.leading,children:b}),i!=null&&i!==``&&(0,n.jsx)(`h1`,{className:t.default.title,"aria-live":`polite`,children:i}),(0,n.jsx)(`div`,{className:t.default.actions,children:d})]})}exports.AppBar=i;
1
+ const e=require("../../utils/cn.cjs"),t=require("./use-sticky-body-warning.cjs"),n=require("./AppBar.module.cjs");let r=require("react/jsx-runtime"),i=require("lucide-react");function a({title:a,leading:o,showBack:s=!1,onBack:c,backLabel:l=`Go back`,backIcon:u,brand:d,actions:f,centered:p=!1,sticky:m=!0,tone:h=`surface`,bordered:g=!0,safeArea:_=!0,className:v,...y}){t.useStickyBodyWarning(m);let b=c??(()=>window.history.back()),x=o??(0,r.jsxs)(r.Fragment,{children:[s&&(0,r.jsx)(`button`,{type:`button`,className:n.default.iconBtn,onClick:b,"aria-label":l,children:u??(0,r.jsx)(i.ArrowLeft,{size:22,"aria-hidden":`true`})}),d&&(0,r.jsx)(`span`,{className:n.default.brand,children:d})]});return(0,r.jsxs)(`header`,{className:e.cn(n.default.bar,n.default[h],p&&n.default.centered,m&&n.default.sticky,g&&n.default.bordered,_&&n.default.safeArea,v),...y,children:[(0,r.jsx)(`div`,{className:n.default.leading,children:x}),a!=null&&a!==``&&(0,r.jsx)(`h1`,{className:n.default.title,"aria-live":`polite`,children:a}),(0,r.jsx)(`div`,{className:n.default.actions,children:f})]})}exports.AppBar=a;
2
2
  //# sourceMappingURL=AppBar.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"AppBar.cjs","names":[],"sources":["../../../src/components/AppBar/AppBar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — a top bar is a slot layout: leading (leading,\n * showBack, onBack, backLabel, backIcon, brand), centre (title, centered) and\n * trailing (actions), plus the chrome it sits in (sticky, tone, bordered, safeArea).\n * Every one of those is a real decision an app makes per screen, and the alternative\n * to accepting them is the app rebuilding the bar around the parts it wanted to\n * change.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./AppBar.module.css\";\n\nexport type AppBarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Page title — string or any node. Rendered as the bar's `<h1>`. */\n title?: ReactNode;\n /**\n * Replace the whole left slot. When set, the auto back button and `brand`\n * are ignored — you own the leading content.\n */\n leading?: ReactNode;\n /** Show a back button in the left slot. Ignored when `leading` is set. */\n showBack?: boolean;\n /**\n * Back-button handler. Defaults to `window.history.back()`. With a router,\n * pass `onBack={() => navigate(-1)}`.\n */\n onBack?: () => void;\n /** Accessible label for the back button. Default `\"Go back\"`. */\n backLabel?: string;\n /** Custom back icon. Default a left arrow. */\n backIcon?: ReactNode;\n /** Brand / logo node shown in the left slot (after the back button). */\n brand?: ReactNode;\n /** Right slot — action buttons / menu. One node or many. */\n actions?: ReactNode;\n /** Center the title (three-column grid). Default `false` (left-aligned). */\n centered?: boolean;\n /** Stick to the top of the scroll container. Default `true`. */\n sticky?: boolean;\n /** Visual tone. Default `\"surface\"`. */\n tone?: AppBarTone;\n /** Thin bottom border. Default `true`. */\n bordered?: boolean;\n /** Add `env(safe-area-inset-top)` padding (iOS notch / PWA). Default `true`. */\n safeArea?: boolean;\n}\n\n/**\n * Mobile-first top app bar for PWAs — leading (back / brand) + title +\n * trailing actions, sticky with safe-area padding out of the box.\n *\n * Consumers customise via tokens (`--tempest-*`) and slots; the SDK ships the\n * layout, sticky/safe-area behaviour, and accessible back button so apps don't\n * hand-roll one per screen. For a desktop three-slot nav use [[Navbar]].\n *\n * @example\n * // Detail screen with back + a trailing action\n * <AppBar\n * title=\"Profile\"\n * showBack\n * onBack={() => navigate(-1)}\n * actions={<IconButton icon={<Settings />} onClick={openSettings} />}\n * />\n *\n * @example\n * // Home screen — brand left, centered title disabled\n * <AppBar brand={<Logo />} actions={<UserMenu />} />\n */\nexport function AppBar({\n title,\n leading,\n showBack = false,\n onBack,\n backLabel = \"Go back\",\n backIcon,\n brand,\n actions,\n centered = false,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n safeArea = true,\n className,\n ...props\n}: AppBarProps) {\n const handleBack = onBack ?? (() => window.history.back());\n\n const leadingContent = leading ?? (\n <>\n {showBack && (\n <button\n type=\"button\"\n className={styles.iconBtn}\n onClick={handleBack}\n aria-label={backLabel}\n >\n {backIcon ?? <ArrowLeft size={22} aria-hidden=\"true\" />}\n </button>\n )}\n {brand && <span className={styles.brand}>{brand}</span>}\n </>\n );\n\n return (\n <header\n className={cn(\n styles.bar,\n styles[tone],\n centered && styles.centered,\n sticky && styles.sticky,\n bordered && styles.bordered,\n safeArea && styles.safeArea,\n className,\n )}\n {...props}\n >\n <div className={styles.leading}>{leadingContent}</div>\n {title != null && title !== \"\" && (\n <h1 className={styles.title} aria-live=\"polite\">\n {title}\n </h1>\n )}\n <div className={styles.actions}>{actions}</div>\n </header>\n );\n}\n"],"mappings":"oIAuEA,SAAgB,EAAO,CACnB,QACA,UACA,WAAW,GACX,SACA,YAAY,UACZ,WACA,QACA,UACA,WAAW,GACX,SAAS,GACT,OAAO,UACP,WAAW,GACX,WAAW,GACX,YACA,GAAG,GACS,CACZ,IAAM,EAAa,QAAiB,OAAO,QAAQ,KAAK,GAElD,EAAiB,IACnB,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,CACK,IACG,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,QAClB,QAAS,EACT,aAAY,EAEX,SAAA,IAAY,EAAA,EAAA,IAAA,CAAC,EAAA,UAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CAClD,CAAA,EAEX,IAAS,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,MAAQ,SAAA,CAAY,CAAA,CACxD,CAAA,CAAA,EAGN,OACI,EAAA,EAAA,KAAA,CAAC,SAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,IACP,EAAA,QAAO,GACP,GAAY,EAAA,QAAO,SACnB,GAAU,EAAA,QAAO,OACjB,GAAY,EAAA,QAAO,SACnB,GAAY,EAAA,QAAO,SACnB,CACJ,EACA,GAAI,EAVR,SAAA,EAYI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAU,SAAA,CAAoB,CAAA,EACpD,GAAS,MAAQ,IAAU,KACxB,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,MAAO,YAAU,SAClC,SAAA,CACD,CAAA,GAER,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAU,SAAA,CAAa,CAAA,CAC1C,GAEhB"}
1
+ {"version":3,"file":"AppBar.cjs","names":[],"sources":["../../../src/components/AppBar/AppBar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — a top bar is a slot layout: leading (leading,\n * showBack, onBack, backLabel, backIcon, brand), centre (title, centered) and\n * trailing (actions), plus the chrome it sits in (sticky, tone, bordered, safeArea).\n * Every one of those is a real decision an app makes per screen, and the alternative\n * to accepting them is the app rebuilding the bar around the parts it wanted to\n * change.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport { useStickyBodyWarning } from \"./use-sticky-body-warning\";\nimport styles from \"./AppBar.module.css\";\n\nexport type AppBarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Page title — string or any node. Rendered as the bar's `<h1>`. */\n title?: ReactNode;\n /**\n * Replace the whole left slot. When set, the auto back button and `brand`\n * are ignored — you own the leading content.\n */\n leading?: ReactNode;\n /** Show a back button in the left slot. Ignored when `leading` is set. */\n showBack?: boolean;\n /**\n * Back-button handler. Defaults to `window.history.back()`. With a router,\n * pass `onBack={() => navigate(-1)}`.\n */\n onBack?: () => void;\n /** Accessible label for the back button. Default `\"Go back\"`. */\n backLabel?: string;\n /** Custom back icon. Default a left arrow. */\n backIcon?: ReactNode;\n /** Brand / logo node shown in the left slot (after the back button). */\n brand?: ReactNode;\n /** Right slot — action buttons / menu. One node or many. */\n actions?: ReactNode;\n /** Center the title (three-column grid). Default `false` (left-aligned). */\n centered?: boolean;\n /**\n * Stick to the top of the scroll container. Default `true`.\n *\n * The page has to leave the body out of the scrolling: `body { overflow-x:\n * hidden }` makes the body a scroll container and the bar scrolls away with\n * the content. Use `overflow-x: clip` on `html` and `body` instead — in\n * development the bar says so in the console when it detects it.\n */\n sticky?: boolean;\n /** Visual tone. Default `\"surface\"`. */\n tone?: AppBarTone;\n /** Thin bottom border. Default `true`. */\n bordered?: boolean;\n /** Add `env(safe-area-inset-top)` padding (iOS notch / PWA). Default `true`. */\n safeArea?: boolean;\n}\n\n/**\n * Mobile-first top app bar for PWAs — leading (back / brand) + title +\n * trailing actions, sticky with safe-area padding out of the box.\n *\n * Consumers customise via tokens (`--tempest-*`) and slots; the SDK ships the\n * layout, sticky/safe-area behaviour, and accessible back button so apps don't\n * hand-roll one per screen. For a desktop three-slot nav use [[Navbar]].\n *\n * @example\n * // Detail screen with back + a trailing action\n * <AppBar\n * title=\"Profile\"\n * showBack\n * onBack={() => navigate(-1)}\n * actions={<IconButton icon={<Settings />} onClick={openSettings} />}\n * />\n *\n * @example\n * // Home screen — brand left, centered title disabled\n * <AppBar brand={<Logo />} actions={<UserMenu />} />\n */\nexport function AppBar({\n title,\n leading,\n showBack = false,\n onBack,\n backLabel = \"Go back\",\n backIcon,\n brand,\n actions,\n centered = false,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n safeArea = true,\n className,\n ...props\n}: AppBarProps) {\n useStickyBodyWarning(sticky);\n\n const handleBack = onBack ?? (() => window.history.back());\n\n const leadingContent = leading ?? (\n <>\n {showBack && (\n <button\n type=\"button\"\n className={styles.iconBtn}\n onClick={handleBack}\n aria-label={backLabel}\n >\n {backIcon ?? <ArrowLeft size={22} aria-hidden=\"true\" />}\n </button>\n )}\n {brand && <span className={styles.brand}>{brand}</span>}\n </>\n );\n\n return (\n <header\n className={cn(\n styles.bar,\n styles[tone],\n centered && styles.centered,\n sticky && styles.sticky,\n bordered && styles.bordered,\n safeArea && styles.safeArea,\n className,\n )}\n {...props}\n >\n <div className={styles.leading}>{leadingContent}</div>\n {title != null && title !== \"\" && (\n <h1 className={styles.title} aria-live=\"polite\">\n {title}\n </h1>\n )}\n <div className={styles.actions}>{actions}</div>\n </header>\n );\n}\n"],"mappings":"+KA+EA,SAAgB,EAAO,CACnB,QACA,UACA,WAAW,GACX,SACA,YAAY,UACZ,WACA,QACA,UACA,WAAW,GACX,SAAS,GACT,OAAO,UACP,WAAW,GACX,WAAW,GACX,YACA,GAAG,GACS,CACZ,EAAA,qBAAqB,CAAM,EAE3B,IAAM,EAAa,QAAiB,OAAO,QAAQ,KAAK,GAElD,EAAiB,IACnB,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,CACK,IACG,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,QAClB,QAAS,EACT,aAAY,EAEX,SAAA,IAAY,EAAA,EAAA,IAAA,CAAC,EAAA,UAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CAClD,CAAA,EAEX,IAAS,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,MAAQ,SAAA,CAAY,CAAA,CACxD,CAAA,CAAA,EAGN,OACI,EAAA,EAAA,KAAA,CAAC,SAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,IACP,EAAA,QAAO,GACP,GAAY,EAAA,QAAO,SACnB,GAAU,EAAA,QAAO,OACjB,GAAY,EAAA,QAAO,SACnB,GAAY,EAAA,QAAO,SACnB,CACJ,EACA,GAAI,EAVR,SAAA,EAYI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAU,SAAA,CAAoB,CAAA,EACpD,GAAS,MAAQ,IAAU,KACxB,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,MAAO,YAAU,SAClC,SAAA,CACD,CAAA,GAER,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAU,SAAA,CAAa,CAAA,CAC1C,GAEhB"}
@@ -1,43 +1,45 @@
1
1
  import { cn as e } from "../../utils/cn.js";
2
- import t from "./AppBar.module.js";
3
- import { Fragment as n, jsx as r, jsxs as i } from "react/jsx-runtime";
4
- import { ArrowLeft as a } from "lucide-react";
2
+ import { useStickyBodyWarning as t } from "./use-sticky-body-warning.js";
3
+ import n from "./AppBar.module.js";
4
+ import { Fragment as r, jsx as i, jsxs as a } from "react/jsx-runtime";
5
+ import { ArrowLeft as o } from "lucide-react";
5
6
  //#region src/components/AppBar/AppBar.tsx
6
- function o({ title: o, leading: s, showBack: c = !1, onBack: l, backLabel: u = "Go back", backIcon: d, brand: f, actions: p, centered: m = !1, sticky: h = !0, tone: g = "surface", bordered: _ = !0, safeArea: v = !0, className: y, ...b }) {
7
- let x = l ?? (() => window.history.back()), S = s ?? /* @__PURE__ */ i(n, { children: [c && /* @__PURE__ */ r("button", {
7
+ function s({ title: s, leading: c, showBack: l = !1, onBack: u, backLabel: d = "Go back", backIcon: f, brand: p, actions: m, centered: h = !1, sticky: g = !0, tone: _ = "surface", bordered: v = !0, safeArea: y = !0, className: b, ...x }) {
8
+ t(g);
9
+ let S = u ?? (() => window.history.back()), C = c ?? /* @__PURE__ */ a(r, { children: [l && /* @__PURE__ */ i("button", {
8
10
  type: "button",
9
- className: t.iconBtn,
10
- onClick: x,
11
- "aria-label": u,
12
- children: d ?? /* @__PURE__ */ r(a, {
11
+ className: n.iconBtn,
12
+ onClick: S,
13
+ "aria-label": d,
14
+ children: f ?? /* @__PURE__ */ i(o, {
13
15
  size: 22,
14
16
  "aria-hidden": "true"
15
17
  })
16
- }), f && /* @__PURE__ */ r("span", {
17
- className: t.brand,
18
- children: f
18
+ }), p && /* @__PURE__ */ i("span", {
19
+ className: n.brand,
20
+ children: p
19
21
  })] });
20
- return /* @__PURE__ */ i("header", {
21
- className: e(t.bar, t[g], m && t.centered, h && t.sticky, _ && t.bordered, v && t.safeArea, y),
22
- ...b,
22
+ return /* @__PURE__ */ a("header", {
23
+ className: e(n.bar, n[_], h && n.centered, g && n.sticky, v && n.bordered, y && n.safeArea, b),
24
+ ...x,
23
25
  children: [
24
- /* @__PURE__ */ r("div", {
25
- className: t.leading,
26
- children: S
26
+ /* @__PURE__ */ i("div", {
27
+ className: n.leading,
28
+ children: C
27
29
  }),
28
- o != null && o !== "" && /* @__PURE__ */ r("h1", {
29
- className: t.title,
30
+ s != null && s !== "" && /* @__PURE__ */ i("h1", {
31
+ className: n.title,
30
32
  "aria-live": "polite",
31
- children: o
33
+ children: s
32
34
  }),
33
- /* @__PURE__ */ r("div", {
34
- className: t.actions,
35
- children: p
35
+ /* @__PURE__ */ i("div", {
36
+ className: n.actions,
37
+ children: m
36
38
  })
37
39
  ]
38
40
  });
39
41
  }
40
42
  //#endregion
41
- export { o as AppBar };
43
+ export { s as AppBar };
42
44
 
43
45
  //# sourceMappingURL=AppBar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AppBar.js","names":[],"sources":["../../../src/components/AppBar/AppBar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — a top bar is a slot layout: leading (leading,\n * showBack, onBack, backLabel, backIcon, brand), centre (title, centered) and\n * trailing (actions), plus the chrome it sits in (sticky, tone, bordered, safeArea).\n * Every one of those is a real decision an app makes per screen, and the alternative\n * to accepting them is the app rebuilding the bar around the parts it wanted to\n * change.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./AppBar.module.css\";\n\nexport type AppBarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Page title — string or any node. Rendered as the bar's `<h1>`. */\n title?: ReactNode;\n /**\n * Replace the whole left slot. When set, the auto back button and `brand`\n * are ignored — you own the leading content.\n */\n leading?: ReactNode;\n /** Show a back button in the left slot. Ignored when `leading` is set. */\n showBack?: boolean;\n /**\n * Back-button handler. Defaults to `window.history.back()`. With a router,\n * pass `onBack={() => navigate(-1)}`.\n */\n onBack?: () => void;\n /** Accessible label for the back button. Default `\"Go back\"`. */\n backLabel?: string;\n /** Custom back icon. Default a left arrow. */\n backIcon?: ReactNode;\n /** Brand / logo node shown in the left slot (after the back button). */\n brand?: ReactNode;\n /** Right slot — action buttons / menu. One node or many. */\n actions?: ReactNode;\n /** Center the title (three-column grid). Default `false` (left-aligned). */\n centered?: boolean;\n /** Stick to the top of the scroll container. Default `true`. */\n sticky?: boolean;\n /** Visual tone. Default `\"surface\"`. */\n tone?: AppBarTone;\n /** Thin bottom border. Default `true`. */\n bordered?: boolean;\n /** Add `env(safe-area-inset-top)` padding (iOS notch / PWA). Default `true`. */\n safeArea?: boolean;\n}\n\n/**\n * Mobile-first top app bar for PWAs — leading (back / brand) + title +\n * trailing actions, sticky with safe-area padding out of the box.\n *\n * Consumers customise via tokens (`--tempest-*`) and slots; the SDK ships the\n * layout, sticky/safe-area behaviour, and accessible back button so apps don't\n * hand-roll one per screen. For a desktop three-slot nav use [[Navbar]].\n *\n * @example\n * // Detail screen with back + a trailing action\n * <AppBar\n * title=\"Profile\"\n * showBack\n * onBack={() => navigate(-1)}\n * actions={<IconButton icon={<Settings />} onClick={openSettings} />}\n * />\n *\n * @example\n * // Home screen — brand left, centered title disabled\n * <AppBar brand={<Logo />} actions={<UserMenu />} />\n */\nexport function AppBar({\n title,\n leading,\n showBack = false,\n onBack,\n backLabel = \"Go back\",\n backIcon,\n brand,\n actions,\n centered = false,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n safeArea = true,\n className,\n ...props\n}: AppBarProps) {\n const handleBack = onBack ?? (() => window.history.back());\n\n const leadingContent = leading ?? (\n <>\n {showBack && (\n <button\n type=\"button\"\n className={styles.iconBtn}\n onClick={handleBack}\n aria-label={backLabel}\n >\n {backIcon ?? <ArrowLeft size={22} aria-hidden=\"true\" />}\n </button>\n )}\n {brand && <span className={styles.brand}>{brand}</span>}\n </>\n );\n\n return (\n <header\n className={cn(\n styles.bar,\n styles[tone],\n centered && styles.centered,\n sticky && styles.sticky,\n bordered && styles.bordered,\n safeArea && styles.safeArea,\n className,\n )}\n {...props}\n >\n <div className={styles.leading}>{leadingContent}</div>\n {title != null && title !== \"\" && (\n <h1 className={styles.title} aria-live=\"polite\">\n {title}\n </h1>\n )}\n <div className={styles.actions}>{actions}</div>\n </header>\n );\n}\n"],"mappings":";;;;;AAuEA,SAAgB,EAAO,EACnB,UACA,YACA,cAAW,IACX,WACA,eAAY,WACZ,aACA,UACA,YACA,cAAW,IACX,YAAS,IACT,UAAO,WACP,cAAW,IACX,cAAW,IACX,cACA,GAAG,KACS;CACZ,IAAM,IAAa,YAAiB,OAAO,QAAQ,KAAK,IAElD,IAAiB,KACnB,kBAAA,GAAA,EAAA,UAAA,CACK,KACG,kBAAC,UAAD;EACI,MAAK;EACL,WAAW,EAAO;EAClB,SAAS;EACT,cAAY;EAEX,UAAA,KAAY,kBAAC,GAAD;GAAW,MAAM;GAAI,eAAY;EAAQ,CAAA;CAClD,CAAA,GAEX,KAAS,kBAAC,QAAD;EAAM,WAAW,EAAO;EAAQ,UAAA;CAAY,CAAA,CACxD,EAAA,CAAA;CAGN,OACI,kBAAC,UAAD;EACI,WAAW,EACP,EAAO,KACP,EAAO,IACP,KAAY,EAAO,UACnB,KAAU,EAAO,QACjB,KAAY,EAAO,UACnB,KAAY,EAAO,UACnB,CACJ;EACA,GAAI;EAVR,UAAA;GAYI,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAU,UAAA;GAAoB,CAAA;GACpD,KAAS,QAAQ,MAAU,MACxB,kBAAC,MAAD;IAAI,WAAW,EAAO;IAAO,aAAU;IAClC,UAAA;GACD,CAAA;GAER,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAU,UAAA;GAAa,CAAA;EAC1C;;AAEhB"}
1
+ {"version":3,"file":"AppBar.js","names":[],"sources":["../../../src/components/AppBar/AppBar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — a top bar is a slot layout: leading (leading,\n * showBack, onBack, backLabel, backIcon, brand), centre (title, centered) and\n * trailing (actions), plus the chrome it sits in (sticky, tone, bordered, safeArea).\n * Every one of those is a real decision an app makes per screen, and the alternative\n * to accepting them is the app rebuilding the bar around the parts it wanted to\n * change.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport { useStickyBodyWarning } from \"./use-sticky-body-warning\";\nimport styles from \"./AppBar.module.css\";\n\nexport type AppBarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Page title — string or any node. Rendered as the bar's `<h1>`. */\n title?: ReactNode;\n /**\n * Replace the whole left slot. When set, the auto back button and `brand`\n * are ignored — you own the leading content.\n */\n leading?: ReactNode;\n /** Show a back button in the left slot. Ignored when `leading` is set. */\n showBack?: boolean;\n /**\n * Back-button handler. Defaults to `window.history.back()`. With a router,\n * pass `onBack={() => navigate(-1)}`.\n */\n onBack?: () => void;\n /** Accessible label for the back button. Default `\"Go back\"`. */\n backLabel?: string;\n /** Custom back icon. Default a left arrow. */\n backIcon?: ReactNode;\n /** Brand / logo node shown in the left slot (after the back button). */\n brand?: ReactNode;\n /** Right slot — action buttons / menu. One node or many. */\n actions?: ReactNode;\n /** Center the title (three-column grid). Default `false` (left-aligned). */\n centered?: boolean;\n /**\n * Stick to the top of the scroll container. Default `true`.\n *\n * The page has to leave the body out of the scrolling: `body { overflow-x:\n * hidden }` makes the body a scroll container and the bar scrolls away with\n * the content. Use `overflow-x: clip` on `html` and `body` instead — in\n * development the bar says so in the console when it detects it.\n */\n sticky?: boolean;\n /** Visual tone. Default `\"surface\"`. */\n tone?: AppBarTone;\n /** Thin bottom border. Default `true`. */\n bordered?: boolean;\n /** Add `env(safe-area-inset-top)` padding (iOS notch / PWA). Default `true`. */\n safeArea?: boolean;\n}\n\n/**\n * Mobile-first top app bar for PWAs — leading (back / brand) + title +\n * trailing actions, sticky with safe-area padding out of the box.\n *\n * Consumers customise via tokens (`--tempest-*`) and slots; the SDK ships the\n * layout, sticky/safe-area behaviour, and accessible back button so apps don't\n * hand-roll one per screen. For a desktop three-slot nav use [[Navbar]].\n *\n * @example\n * // Detail screen with back + a trailing action\n * <AppBar\n * title=\"Profile\"\n * showBack\n * onBack={() => navigate(-1)}\n * actions={<IconButton icon={<Settings />} onClick={openSettings} />}\n * />\n *\n * @example\n * // Home screen — brand left, centered title disabled\n * <AppBar brand={<Logo />} actions={<UserMenu />} />\n */\nexport function AppBar({\n title,\n leading,\n showBack = false,\n onBack,\n backLabel = \"Go back\",\n backIcon,\n brand,\n actions,\n centered = false,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n safeArea = true,\n className,\n ...props\n}: AppBarProps) {\n useStickyBodyWarning(sticky);\n\n const handleBack = onBack ?? (() => window.history.back());\n\n const leadingContent = leading ?? (\n <>\n {showBack && (\n <button\n type=\"button\"\n className={styles.iconBtn}\n onClick={handleBack}\n aria-label={backLabel}\n >\n {backIcon ?? <ArrowLeft size={22} aria-hidden=\"true\" />}\n </button>\n )}\n {brand && <span className={styles.brand}>{brand}</span>}\n </>\n );\n\n return (\n <header\n className={cn(\n styles.bar,\n styles[tone],\n centered && styles.centered,\n sticky && styles.sticky,\n bordered && styles.bordered,\n safeArea && styles.safeArea,\n className,\n )}\n {...props}\n >\n <div className={styles.leading}>{leadingContent}</div>\n {title != null && title !== \"\" && (\n <h1 className={styles.title} aria-live=\"polite\">\n {title}\n </h1>\n )}\n <div className={styles.actions}>{actions}</div>\n </header>\n );\n}\n"],"mappings":";;;;;;AA+EA,SAAgB,EAAO,EACnB,UACA,YACA,cAAW,IACX,WACA,eAAY,WACZ,aACA,UACA,YACA,cAAW,IACX,YAAS,IACT,UAAO,WACP,cAAW,IACX,cAAW,IACX,cACA,GAAG,KACS;CACZ,EAAqB,CAAM;CAE3B,IAAM,IAAa,YAAiB,OAAO,QAAQ,KAAK,IAElD,IAAiB,KACnB,kBAAA,GAAA,EAAA,UAAA,CACK,KACG,kBAAC,UAAD;EACI,MAAK;EACL,WAAW,EAAO;EAClB,SAAS;EACT,cAAY;EAEX,UAAA,KAAY,kBAAC,GAAD;GAAW,MAAM;GAAI,eAAY;EAAQ,CAAA;CAClD,CAAA,GAEX,KAAS,kBAAC,QAAD;EAAM,WAAW,EAAO;EAAQ,UAAA;CAAY,CAAA,CACxD,EAAA,CAAA;CAGN,OACI,kBAAC,UAAD;EACI,WAAW,EACP,EAAO,KACP,EAAO,IACP,KAAY,EAAO,UACnB,KAAU,EAAO,QACjB,KAAY,EAAO,UACnB,KAAY,EAAO,UACnB,CACJ;EACA,GAAI;EAVR,UAAA;GAYI,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAU,UAAA;GAAoB,CAAA;GACpD,KAAS,QAAQ,MAAU,MACxB,kBAAC,MAAD;IAAI,WAAW,EAAO;IAAO,aAAU;IAClC,UAAA;GACD,CAAA;GAER,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAU,UAAA;GAAa,CAAA;EAC1C;;AAEhB"}
@@ -0,0 +1,2 @@
1
+ let e=require("react");var t="[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has `overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the body a scroll container. The bar then pins to the body's scrollport instead of the viewport and scrolls off the top with the content — most visibly on Chrome Android. Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. Pass `sticky={false}` if the bar is meant to scroll away.";function n(){return!1}var r=!1;function i(i){(0,e.useEffect)(()=>{!i||r||!n()||typeof document>`u`||!document.body||getComputedStyle(document.body).overflowX===`hidden`&&(r=!0,console.warn(t))},[i])}exports.useStickyBodyWarning=i;
2
+ //# sourceMappingURL=use-sticky-body-warning.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-sticky-body-warning.cjs","names":[],"sources":["../../../src/components/AppBar/use-sticky-body-warning.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\n/**\n * The one line of the app's own CSS that silently defeats `sticky`.\n *\n * `body { overflow-x: hidden }` is how most apps stop a too-wide child from\n * panning the page sideways. It also, per CSS Overflow 3, forces the computed\n * `overflow-y` to `auto` — a value the author never wrote — which makes the\n * body a **scroll container**. Every sticky descendant then pins to the body's\n * scrollport instead of the viewport, and that scrollport travels with the\n * document: measured in Chromium at 390px with a long page, the bar sat at\n * `top: -900px` after scrolling 900px, i.e. off screen.\n *\n * Nothing about that reads as a CSS problem from the app's side. The bar still\n * reports `position: sticky` in DevTools, the markup is unchanged, and on\n * desktop the screens usually fit without scrolling so the bug never shows. It\n * surfaces on Chrome Android, where the collapsing URL bar makes every screen a\n * scrolling screen — and it gets reported as \"the app bar is broken\", because\n * that is what the user sees.\n *\n * Hence this warning: the SDK cannot fix it from inside the component (a sticky\n * element has no way to opt out of its scroll container, and a rule the SDK\n * ships in its reset loses to the app's own stylesheet, which is imported\n * after), but it can name the cause in one console line.\n *\n * `clip` is the fix because it clamps the same horizontal overflow without\n * creating a scroll container. It has to be on `html` **and** `body` — with it\n * on only one of them the document still panned to `x: 500` in the same\n * measurement.\n */\nconst MESSAGE =\n \"[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has \" +\n \"`overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the \" +\n \"body a scroll container. The bar then pins to the body's scrollport instead of the \" +\n \"viewport and scrolls off the top with the content — most visibly on Chrome Android. \" +\n \"Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. \" +\n \"Pass `sticky={false}` if the bar is meant to scroll away.\";\n\n/**\n * Whether the bundle was built for development.\n *\n * Guarded because the SDK also runs where `import.meta.env` does not exist — a\n * service-worker context, a Node test runner, a build-time script.\n */\nfunction isDev(): boolean {\n return Boolean(import.meta.env?.DEV);\n}\n\n/** Set once the warning has been printed, so re-renders do not bury the console. */\nlet warned = false;\n\n/**\n * Reset the warn-once latch. Test-only — the latch is module state, and a suite\n * that renders more than one bar would otherwise see the warning only once.\n */\nexport function resetStickyBodyWarning(): void {\n warned = false;\n}\n\n/**\n * Warn once, in development, when the page's own CSS will keep a sticky bar\n * from sticking.\n *\n * Reads the body's `overflow-x` rather than walking the ancestors, because that\n * single declaration is the whole failure mode: an app that genuinely wants the\n * body to scroll writes `overflow-y: auto` itself, and a sticky bar inside a\n * scroll container the app chose on purpose behaves exactly as intended. What\n * is never intentional is acquiring that scroll container as a side effect of\n * clamping the other axis.\n *\n * @param enabled - Whether the bar is actually sticky. A bar rendered with\n * `sticky={false}` has nothing to lose and stays quiet.\n */\nexport function useStickyBodyWarning(enabled: boolean): void {\n useEffect(() => {\n if (!enabled || warned || !isDev()) return;\n if (typeof document === \"undefined\" || !document.body) return;\n if (getComputedStyle(document.body).overflowX !== \"hidden\") return;\n warned = true;\n console.warn(MESSAGE);\n }, [enabled]);\n}\n"],"mappings":"uBA8BA,IAAM,EACF,wdAaJ,SAAS,GAAiB,CACtB,MAAO,CAAA,CACX,CAGA,IAAI,EAAS,GAwBb,SAAgB,EAAqB,EAAwB,EACzD,EAAA,EAAA,UAAA,KAAgB,CACR,CAAC,GAAW,GAAU,CAAC,EAAM,GAC7B,OAAO,SAAa,KAAe,CAAC,SAAS,MAC7C,iBAAiB,SAAS,IAAI,CAAC,CAAC,YAAc,WAClD,EAAS,GACT,QAAQ,KAAK,CAAO,EACxB,EAAG,CAAC,CAAO,CAAC,CAChB"}
@@ -0,0 +1,16 @@
1
+ import { useEffect as e } from "react";
2
+ //#region src/components/AppBar/use-sticky-body-warning.ts
3
+ var t = "[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has `overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the body a scroll container. The bar then pins to the body's scrollport instead of the viewport and scrolls off the top with the content — most visibly on Chrome Android. Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. Pass `sticky={false}` if the bar is meant to scroll away.";
4
+ function n() {
5
+ return !1;
6
+ }
7
+ var r = !1;
8
+ function i(i) {
9
+ e(() => {
10
+ !i || r || !n() || typeof document > "u" || !document.body || getComputedStyle(document.body).overflowX === "hidden" && (r = !0, console.warn(t));
11
+ }, [i]);
12
+ }
13
+ //#endregion
14
+ export { i as useStickyBodyWarning };
15
+
16
+ //# sourceMappingURL=use-sticky-body-warning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-sticky-body-warning.js","names":[],"sources":["../../../src/components/AppBar/use-sticky-body-warning.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\n/**\n * The one line of the app's own CSS that silently defeats `sticky`.\n *\n * `body { overflow-x: hidden }` is how most apps stop a too-wide child from\n * panning the page sideways. It also, per CSS Overflow 3, forces the computed\n * `overflow-y` to `auto` — a value the author never wrote — which makes the\n * body a **scroll container**. Every sticky descendant then pins to the body's\n * scrollport instead of the viewport, and that scrollport travels with the\n * document: measured in Chromium at 390px with a long page, the bar sat at\n * `top: -900px` after scrolling 900px, i.e. off screen.\n *\n * Nothing about that reads as a CSS problem from the app's side. The bar still\n * reports `position: sticky` in DevTools, the markup is unchanged, and on\n * desktop the screens usually fit without scrolling so the bug never shows. It\n * surfaces on Chrome Android, where the collapsing URL bar makes every screen a\n * scrolling screen — and it gets reported as \"the app bar is broken\", because\n * that is what the user sees.\n *\n * Hence this warning: the SDK cannot fix it from inside the component (a sticky\n * element has no way to opt out of its scroll container, and a rule the SDK\n * ships in its reset loses to the app's own stylesheet, which is imported\n * after), but it can name the cause in one console line.\n *\n * `clip` is the fix because it clamps the same horizontal overflow without\n * creating a scroll container. It has to be on `html` **and** `body` — with it\n * on only one of them the document still panned to `x: 500` in the same\n * measurement.\n */\nconst MESSAGE =\n \"[tempest-react-sdk] <AppBar sticky /> will not stick: `document.body` has \" +\n \"`overflow-x: hidden`, which forces its computed `overflow-y` to `auto` and makes the \" +\n \"body a scroll container. The bar then pins to the body's scrollport instead of the \" +\n \"viewport and scrolls off the top with the content — most visibly on Chrome Android. \" +\n \"Fix: `html, body { overflow-x: clip }`, on both elements, since neither clamps alone. \" +\n \"Pass `sticky={false}` if the bar is meant to scroll away.\";\n\n/**\n * Whether the bundle was built for development.\n *\n * Guarded because the SDK also runs where `import.meta.env` does not exist — a\n * service-worker context, a Node test runner, a build-time script.\n */\nfunction isDev(): boolean {\n return Boolean(import.meta.env?.DEV);\n}\n\n/** Set once the warning has been printed, so re-renders do not bury the console. */\nlet warned = false;\n\n/**\n * Reset the warn-once latch. Test-only — the latch is module state, and a suite\n * that renders more than one bar would otherwise see the warning only once.\n */\nexport function resetStickyBodyWarning(): void {\n warned = false;\n}\n\n/**\n * Warn once, in development, when the page's own CSS will keep a sticky bar\n * from sticking.\n *\n * Reads the body's `overflow-x` rather than walking the ancestors, because that\n * single declaration is the whole failure mode: an app that genuinely wants the\n * body to scroll writes `overflow-y: auto` itself, and a sticky bar inside a\n * scroll container the app chose on purpose behaves exactly as intended. What\n * is never intentional is acquiring that scroll container as a side effect of\n * clamping the other axis.\n *\n * @param enabled - Whether the bar is actually sticky. A bar rendered with\n * `sticky={false}` has nothing to lose and stays quiet.\n */\nexport function useStickyBodyWarning(enabled: boolean): void {\n useEffect(() => {\n if (!enabled || warned || !isDev()) return;\n if (typeof document === \"undefined\" || !document.body) return;\n if (getComputedStyle(document.body).overflowX !== \"hidden\") return;\n warned = true;\n console.warn(MESSAGE);\n }, [enabled]);\n}\n"],"mappings":";;AA8BA,IAAM,IACF;AAaJ,SAAS,IAAiB;CACtB,OAAO,CAAA;AACX;AAGA,IAAI,IAAS;AAwBb,SAAgB,EAAqB,GAAwB;CACzD,QAAgB;EACR,CAAC,KAAW,KAAU,CAAC,EAAM,KAC7B,OAAO,WAAa,OAAe,CAAC,SAAS,QAC7C,iBAAiB,SAAS,IAAI,CAAC,CAAC,cAAc,aAClD,IAAS,IACT,QAAQ,KAAK,CAAO;CACxB,GAAG,CAAC,CAAO,CAAC;AAChB"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("./bar-list-model.cjs"),n=require("./BarList.module.cjs");let r=require("react/jsx-runtime");function i({items:i,valueFormatter:a,showPercentage:o=!1,sort:s=`desc`,max:c,otherLabel:l,className:u,...d}){let f=t.buildBarListRows(i,s,c,l);return(0,r.jsx)(`ul`,{className:e.cn(n.default.list,u),...d,children:f.map(e=>(0,r.jsxs)(`li`,{className:n.default.item,children:[(0,r.jsxs)(`div`,{className:n.default.line,children:[(0,r.jsx)(`span`,{className:n.default.label,children:e.label}),(0,r.jsxs)(`span`,{className:n.default.value,children:[a?a(e.value):e.value,o&&(0,r.jsxs)(`span`,{className:n.default.percentage,children:[Math.round(e.percentage),`%`]})]})]}),(0,r.jsx)(`div`,{className:n.default.track,"aria-hidden":`true`,children:(0,r.jsx)(`div`,{className:n.default.fill,style:{width:`${e.width}%`,backgroundColor:e.color??`var(--tempest-chart-${e.index%8+1})`}})})]},`${e.label}-${e.index}`))})}exports.BarList=i;
2
+ //# sourceMappingURL=BarList.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BarList.cjs","names":[],"sources":["../../../src/components/BarList/BarList.tsx"],"sourcesContent":["import type { HTMLAttributes } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { buildBarListRows, type BarListItem, type BarListSort } from \"./bar-list-model\";\nimport styles from \"./BarList.module.css\";\n\nexport type { BarListItem, BarListSort } from \"./bar-list-model\";\n\nexport interface BarListProps extends Omit<HTMLAttributes<HTMLUListElement>, \"children\"> {\n /** The rows. Entries whose value is not finite are dropped. */\n items: readonly BarListItem[];\n /** Render the number. Defaults to the value as-is. */\n valueFormatter?: (value: number) => string;\n /** Show each row's share of the total next to its value. Default `false`. */\n showPercentage?: boolean;\n /** Ordering. Default `\"desc\"`, which is what a ranking means. */\n sort?: BarListSort;\n /** Keep at most this many rows. */\n max?: number;\n /**\n * Label for one aggregated row holding everything `max` cut off.\n *\n * Without it, `max` simply truncates. The aggregate row only appears when more\n * than one row was cut — collapsing a single row into \"others\" hides its name\n * for nothing.\n */\n otherLabel?: string;\n}\n\n/**\n * A ranked distribution: label, proportional bar, value, optional share.\n *\n * The most common chart on an admin panel, and the one the SDK kept sending\n * people to write by hand — `Progress` is a single bar and `Sparkline` is a time\n * series, so a \"users per plan\" block ended up reimplemented per screen, each\n * with its own CSS and its own `.sort()`.\n *\n * It is a **list**, not a picture: `<ul>` / `<li>` with the value written as\n * text, the bar `aria-hidden` behind it. A screen reader reads \"Free, 128, 62%\"\n * because that text is there, not because an `aria-label` restates a drawing.\n * Which is also why the label never sits on top of the bar — text over a tinted\n * fill has to be re-verified against that fill, and the SDK has been caught by\n * that twice.\n *\n * Bar width is relative to the **largest** row, so the biggest bar fills the\n * track; the percentage is the row's share of the **total**. They are different\n * numbers on purpose: a width scaled by the total leaves every bar short in a\n * long list, which is when the chart is needed most.\n *\n * @example\n * <BarList\n * items={[{ label: \"Free\", value: 128 }, { label: \"Pro\", value: 32 }]}\n * valueFormatter={(n) => `${n} ativos`}\n * showPercentage\n * max={5}\n * otherLabel=\"Outros\"\n * />\n *\n * @param props - Items plus presentation.\n * @returns The list.\n */\nexport function BarList({\n items,\n valueFormatter,\n showPercentage = false,\n sort = \"desc\",\n max,\n otherLabel,\n className,\n ...rest\n}: BarListProps) {\n const rows = buildBarListRows(items, sort, max, otherLabel);\n\n return (\n <ul className={cn(styles.list, className)} {...rest}>\n {rows.map((row) => (\n <li key={`${row.label}-${row.index}`} className={styles.item}>\n <div className={styles.line}>\n <span className={styles.label}>{row.label}</span>\n <span className={styles.value}>\n {valueFormatter ? valueFormatter(row.value) : row.value}\n {showPercentage && (\n <span className={styles.percentage}>\n {Math.round(row.percentage)}%\n </span>\n )}\n </span>\n </div>\n <div className={styles.track} aria-hidden=\"true\">\n <div\n className={styles.fill}\n style={{\n width: `${row.width}%`,\n backgroundColor:\n row.color ?? `var(--tempest-chart-${(row.index % 8) + 1})`,\n }}\n />\n </div>\n </li>\n ))}\n </ul>\n );\n}\n"],"mappings":"6IA8DA,SAAgB,EAAQ,CACpB,QACA,iBACA,iBAAiB,GACjB,OAAO,OACP,MACA,aACA,YACA,GAAG,GACU,CACb,IAAM,EAAO,EAAA,iBAAiB,EAAO,EAAM,EAAK,CAAU,EAE1D,OACI,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,CAAS,EAAG,GAAI,EAC1C,SAAA,EAAK,IAAK,IACP,EAAA,EAAA,KAAA,CAAC,KAAD,CAAsC,UAAW,EAAA,QAAO,KAAxD,SAAA,EACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAvB,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,MAAQ,SAAA,EAAI,KAAY,CAAA,GAChD,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,MAAxB,SAAA,CACK,EAAiB,EAAe,EAAI,KAAK,EAAI,EAAI,MACjD,IACG,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,WAAxB,SAAA,CACK,KAAK,MAAM,EAAI,UAAU,EAAE,GAC1B,CAER,CAAA,CAAA,CACL,CAAA,CAAA,CACL,CAAA,GAAA,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,MAAO,cAAY,OACtC,UAAA,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,KAClB,MAAO,CACH,MAAO,GAAG,EAAI,MAAM,GACpB,gBACI,EAAI,OAAS,uBAAwB,EAAI,MAAQ,EAAK,EAAE,EAChE,CACH,CAAA,CACA,CAAA,CACL,CAtBK,EAAA,GAAG,EAAI,MAAM,GAAG,EAAI,OAsBzB,CACP,CACD,CAAA,CAEZ"}
@@ -0,0 +1,42 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { buildBarListRows as t } from "./bar-list-model.js";
3
+ import n from "./BarList.module.js";
4
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
+ //#region src/components/BarList/BarList.tsx
6
+ function a({ items: a, valueFormatter: o, showPercentage: s = !1, sort: c = "desc", max: l, otherLabel: u, className: d, ...f }) {
7
+ let p = t(a, c, l, u);
8
+ return /* @__PURE__ */ r("ul", {
9
+ className: e(n.list, d),
10
+ ...f,
11
+ children: p.map((e) => /* @__PURE__ */ i("li", {
12
+ className: n.item,
13
+ children: [/* @__PURE__ */ i("div", {
14
+ className: n.line,
15
+ children: [/* @__PURE__ */ r("span", {
16
+ className: n.label,
17
+ children: e.label
18
+ }), /* @__PURE__ */ i("span", {
19
+ className: n.value,
20
+ children: [o ? o(e.value) : e.value, s && /* @__PURE__ */ i("span", {
21
+ className: n.percentage,
22
+ children: [Math.round(e.percentage), "%"]
23
+ })]
24
+ })]
25
+ }), /* @__PURE__ */ r("div", {
26
+ className: n.track,
27
+ "aria-hidden": "true",
28
+ children: /* @__PURE__ */ r("div", {
29
+ className: n.fill,
30
+ style: {
31
+ width: `${e.width}%`,
32
+ backgroundColor: e.color ?? `var(--tempest-chart-${e.index % 8 + 1})`
33
+ }
34
+ })
35
+ })]
36
+ }, `${e.label}-${e.index}`))
37
+ });
38
+ }
39
+ //#endregion
40
+ export { a as BarList };
41
+
42
+ //# sourceMappingURL=BarList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BarList.js","names":[],"sources":["../../../src/components/BarList/BarList.tsx"],"sourcesContent":["import type { HTMLAttributes } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { buildBarListRows, type BarListItem, type BarListSort } from \"./bar-list-model\";\nimport styles from \"./BarList.module.css\";\n\nexport type { BarListItem, BarListSort } from \"./bar-list-model\";\n\nexport interface BarListProps extends Omit<HTMLAttributes<HTMLUListElement>, \"children\"> {\n /** The rows. Entries whose value is not finite are dropped. */\n items: readonly BarListItem[];\n /** Render the number. Defaults to the value as-is. */\n valueFormatter?: (value: number) => string;\n /** Show each row's share of the total next to its value. Default `false`. */\n showPercentage?: boolean;\n /** Ordering. Default `\"desc\"`, which is what a ranking means. */\n sort?: BarListSort;\n /** Keep at most this many rows. */\n max?: number;\n /**\n * Label for one aggregated row holding everything `max` cut off.\n *\n * Without it, `max` simply truncates. The aggregate row only appears when more\n * than one row was cut — collapsing a single row into \"others\" hides its name\n * for nothing.\n */\n otherLabel?: string;\n}\n\n/**\n * A ranked distribution: label, proportional bar, value, optional share.\n *\n * The most common chart on an admin panel, and the one the SDK kept sending\n * people to write by hand — `Progress` is a single bar and `Sparkline` is a time\n * series, so a \"users per plan\" block ended up reimplemented per screen, each\n * with its own CSS and its own `.sort()`.\n *\n * It is a **list**, not a picture: `<ul>` / `<li>` with the value written as\n * text, the bar `aria-hidden` behind it. A screen reader reads \"Free, 128, 62%\"\n * because that text is there, not because an `aria-label` restates a drawing.\n * Which is also why the label never sits on top of the bar — text over a tinted\n * fill has to be re-verified against that fill, and the SDK has been caught by\n * that twice.\n *\n * Bar width is relative to the **largest** row, so the biggest bar fills the\n * track; the percentage is the row's share of the **total**. They are different\n * numbers on purpose: a width scaled by the total leaves every bar short in a\n * long list, which is when the chart is needed most.\n *\n * @example\n * <BarList\n * items={[{ label: \"Free\", value: 128 }, { label: \"Pro\", value: 32 }]}\n * valueFormatter={(n) => `${n} ativos`}\n * showPercentage\n * max={5}\n * otherLabel=\"Outros\"\n * />\n *\n * @param props - Items plus presentation.\n * @returns The list.\n */\nexport function BarList({\n items,\n valueFormatter,\n showPercentage = false,\n sort = \"desc\",\n max,\n otherLabel,\n className,\n ...rest\n}: BarListProps) {\n const rows = buildBarListRows(items, sort, max, otherLabel);\n\n return (\n <ul className={cn(styles.list, className)} {...rest}>\n {rows.map((row) => (\n <li key={`${row.label}-${row.index}`} className={styles.item}>\n <div className={styles.line}>\n <span className={styles.label}>{row.label}</span>\n <span className={styles.value}>\n {valueFormatter ? valueFormatter(row.value) : row.value}\n {showPercentage && (\n <span className={styles.percentage}>\n {Math.round(row.percentage)}%\n </span>\n )}\n </span>\n </div>\n <div className={styles.track} aria-hidden=\"true\">\n <div\n className={styles.fill}\n style={{\n width: `${row.width}%`,\n backgroundColor:\n row.color ?? `var(--tempest-chart-${(row.index % 8) + 1})`,\n }}\n />\n </div>\n </li>\n ))}\n </ul>\n );\n}\n"],"mappings":";;;;;AA8DA,SAAgB,EAAQ,EACpB,UACA,mBACA,oBAAiB,IACjB,UAAO,QACP,QACA,eACA,cACA,GAAG,KACU;CACb,IAAM,IAAO,EAAiB,GAAO,GAAM,GAAK,CAAU;CAE1D,OACI,kBAAC,MAAD;EAAI,WAAW,EAAG,EAAO,MAAM,CAAS;EAAG,GAAI;EAC1C,UAAA,EAAK,KAAK,MACP,kBAAC,MAAD;GAAsC,WAAW,EAAO;GAAxD,UAAA,CACI,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAvB,UAAA,CACI,kBAAC,QAAD;KAAM,WAAW,EAAO;KAAQ,UAAA,EAAI;IAAY,CAAA,GAChD,kBAAC,QAAD;KAAM,WAAW,EAAO;KAAxB,UAAA,CACK,IAAiB,EAAe,EAAI,KAAK,IAAI,EAAI,OACjD,KACG,kBAAC,QAAD;MAAM,WAAW,EAAO;MAAxB,UAAA,CACK,KAAK,MAAM,EAAI,UAAU,GAAE,GAC1B;KAER,CAAA,CAAA;IACL,CAAA,CAAA;GACL,CAAA,GAAA,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAO,eAAY;IACtC,UAAA,kBAAC,OAAD;KACI,WAAW,EAAO;KAClB,OAAO;MACH,OAAO,GAAG,EAAI,MAAM;MACpB,iBACI,EAAI,SAAS,uBAAwB,EAAI,QAAQ,IAAK,EAAE;KAChE;IACH,CAAA;GACA,CAAA,CACL;EAtBK,GAAA,GAAG,EAAI,MAAM,GAAG,EAAI,OAsBzB,CACP;CACD,CAAA;AAEZ"}
@@ -0,0 +1,2 @@
1
+ var e=`tempest_list_XtltA`,t=`tempest_item_y-W9f`,n=`tempest_line_WKQ57`,r=`tempest_label_AywVf`,i=`tempest_value_Y0DD0`,a=`tempest_percentage_YTyMa`,o=`tempest_track_OVSC3`,s=`tempest_fill_kg-Vl`,c={list:e,item:t,line:n,label:r,value:i,percentage:a,track:o,fill:s};exports.default=c,exports.fill=s,exports.item=t,exports.label=r,exports.line=n,exports.list=e,exports.percentage=a,exports.track=o,exports.value=i;
2
+ //# sourceMappingURL=BarList.module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BarList.module.cjs","names":[],"sources":["../../../src/components/BarList/BarList.module.css"],"sourcesContent":[".list {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-3, 0.75rem);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.item {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1, 0.25rem);\n}\n\n.line {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--tempest-space-3, 0.75rem);\n font-size: var(--tempest-font-size-sm, 0.8125rem);\n}\n\n/*\n * The label truncates instead of wrapping: a plan name long enough to wrap would\n * push its own bar down and break the alignment the list exists to show.\n */\n.label {\n min-width: 0;\n overflow: hidden;\n color: var(--tempest-text);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.value {\n display: inline-flex;\n align-items: baseline;\n gap: var(--tempest-space-2, 0.5rem);\n color: var(--tempest-text);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n/*\n * The share is de-emphasised by size, and only then by colour — and the colour it\n * uses is resolved against the page background, which is where this text actually\n * sits. Putting the label on top of the bar would need the fill's own foreground\n * re-verified per series, which is the trap this layout avoids entirely.\n */\n.percentage {\n color: var(--tempest-text-subtle);\n font-size: var(--tempest-font-size-xs, 0.75rem);\n}\n\n.track {\n overflow: hidden;\n width: 100%;\n height: 0.5rem;\n border-radius: var(--tempest-radius-sm, 0.25rem);\n background-color: var(--tempest-surface-2);\n}\n\n.fill {\n height: 100%;\n border-radius: inherit;\n transition: width var(--tempest-duration-normal, 200ms) ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fill {\n transition: none;\n }\n}\n"],"mappings":""}
@@ -0,0 +1,15 @@
1
+ //#region src/components/BarList/BarList.module.css
2
+ var e = "tempest_list_XtltA", t = "tempest_item_y-W9f", n = "tempest_line_WKQ57", r = "tempest_label_AywVf", i = "tempest_value_Y0DD0", a = "tempest_percentage_YTyMa", o = "tempest_track_OVSC3", s = "tempest_fill_kg-Vl", c = {
3
+ list: e,
4
+ item: t,
5
+ line: n,
6
+ label: r,
7
+ value: i,
8
+ percentage: a,
9
+ track: o,
10
+ fill: s
11
+ };
12
+ //#endregion
13
+ export { c as default, s as fill, t as item, r as label, n as line, e as list, a as percentage, o as track, i as value };
14
+
15
+ //# sourceMappingURL=BarList.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BarList.module.js","names":[],"sources":["../../../src/components/BarList/BarList.module.css"],"sourcesContent":[".list {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-3, 0.75rem);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.item {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1, 0.25rem);\n}\n\n.line {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--tempest-space-3, 0.75rem);\n font-size: var(--tempest-font-size-sm, 0.8125rem);\n}\n\n/*\n * The label truncates instead of wrapping: a plan name long enough to wrap would\n * push its own bar down and break the alignment the list exists to show.\n */\n.label {\n min-width: 0;\n overflow: hidden;\n color: var(--tempest-text);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.value {\n display: inline-flex;\n align-items: baseline;\n gap: var(--tempest-space-2, 0.5rem);\n color: var(--tempest-text);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n/*\n * The share is de-emphasised by size, and only then by colour — and the colour it\n * uses is resolved against the page background, which is where this text actually\n * sits. Putting the label on top of the bar would need the fill's own foreground\n * re-verified per series, which is the trap this layout avoids entirely.\n */\n.percentage {\n color: var(--tempest-text-subtle);\n font-size: var(--tempest-font-size-xs, 0.75rem);\n}\n\n.track {\n overflow: hidden;\n width: 100%;\n height: 0.5rem;\n border-radius: var(--tempest-radius-sm, 0.25rem);\n background-color: var(--tempest-surface-2);\n}\n\n.fill {\n height: 100%;\n border-radius: inherit;\n transition: width var(--tempest-duration-normal, 200ms) ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fill {\n transition: none;\n }\n}\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/numbers.cjs");function t(t,n,r,i){let a=t.filter(e=>Number.isFinite(e.value)),o=a.reduce((e,t)=>e+Math.max(t.value,0),0),s=n===`none`?[...a]:[...a].sort((e,t)=>n===`asc`?e.value-t.value:t.value-e.value),c=r!==void 0&&r>0?s.slice(0,r):s,l=r!==void 0&&r>0?s.slice(r):[],u=i!==void 0&&l.length>1?[...c,{label:i,value:l.reduce((e,t)=>e+t.value,0)}]:i!==void 0&&l.length===1?[...c,...l]:c,d=u.reduce((e,t)=>Math.max(e,t.value),0);return u.map((t,n)=>({...t,index:n,percentage:e.percentOf(Math.max(t.value,0),o),width:e.percentOf(Math.max(t.value,0),d)}))}exports.buildBarListRows=t;
2
+ //# sourceMappingURL=bar-list-model.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bar-list-model.cjs","names":[],"sources":["../../../src/components/BarList/bar-list-model.ts"],"sourcesContent":["// The arithmetic behind BarList, kept out of the component so the decisions that\n// are easy to get wrong — an empty total, a negative value, what \"100% wide\"\n// means — are testable without rendering anything.\n\nimport { percentOf } from \"@/utils/numbers\";\n\n/** One entry of a {@link BarList}. */\nexport interface BarListItem {\n /** What the row is called. */\n label: string;\n /** The measured amount. */\n value: number;\n /** Override the bar colour. Defaults to the chart series token for its position. */\n color?: string;\n}\n\n/** How the list orders itself before drawing. */\nexport type BarListSort = \"desc\" | \"asc\" | \"none\";\n\n/** One row, with everything the component needs to draw it. */\nexport interface BarListRow extends BarListItem {\n /** Share of the total, 0–100. */\n percentage: number;\n /** Bar width relative to the largest row, 0–100. */\n width: number;\n /** Position in the original palette cycle. */\n index: number;\n}\n\n/**\n * Order, truncate and measure the rows.\n *\n * Two different numbers come out of this, and conflating them is the classic bug\n * in a hand-written bar list:\n *\n * - `percentage` is the row's share of the **total**, which is what the label\n * claims when it reads \"32%\".\n * - `width` is relative to the **largest** row, so the biggest bar fills the\n * track. Scaling width by the total instead leaves every bar short in a list of\n * many small values, and the chart stops being readable exactly when it has the\n * most rows.\n *\n * The total counts positive values only. A negative amount draws no bar (a bar of\n * negative width does not exist) and reports 0%, but its number is still shown —\n * hiding the row would be worse than showing an odd one.\n *\n * @param items - The rows as given.\n * @param sort - Ordering to apply. `\"none\"` keeps the caller's order.\n * @param max - Keep at most this many rows.\n * @param otherLabel - Aggregate what `max` cut into one row with this label.\n * @returns The rows to draw, in order.\n */\nexport function buildBarListRows(\n items: readonly BarListItem[],\n sort: BarListSort,\n max: number | undefined,\n otherLabel: string | undefined,\n): BarListRow[] {\n const usable = items.filter((item) => Number.isFinite(item.value));\n const total = usable.reduce((sum, item) => sum + Math.max(item.value, 0), 0);\n\n const ordered =\n sort === \"none\"\n ? [...usable]\n : [...usable].sort((a, b) => (sort === \"asc\" ? a.value - b.value : b.value - a.value));\n\n const kept = max !== undefined && max > 0 ? ordered.slice(0, max) : ordered;\n const cut = max !== undefined && max > 0 ? ordered.slice(max) : [];\n\n const shown: BarListItem[] =\n otherLabel !== undefined && cut.length > 1\n ? [\n ...kept,\n {\n label: otherLabel,\n value: cut.reduce((sum, item) => sum + item.value, 0),\n },\n ]\n : otherLabel !== undefined && cut.length === 1\n ? [...kept, ...cut]\n : kept;\n\n const largest = shown.reduce((high, item) => Math.max(high, item.value), 0);\n\n return shown.map((item, index) => ({\n ...item,\n index,\n percentage: percentOf(Math.max(item.value, 0), total),\n width: percentOf(Math.max(item.value, 0), largest),\n }));\n}\n"],"mappings":"2CAoDA,SAAgB,EACZ,EACA,EACA,EACA,EACY,CACZ,IAAM,EAAS,EAAM,OAAQ,GAAS,OAAO,SAAS,EAAK,KAAK,CAAC,EAC3D,EAAQ,EAAO,QAAQ,EAAK,IAAS,EAAM,KAAK,IAAI,EAAK,MAAO,CAAC,EAAG,CAAC,EAErE,EACF,IAAS,OACH,CAAC,GAAG,CAAM,EACV,CAAC,GAAG,CAAM,CAAC,CAAC,MAAM,EAAG,IAAO,IAAS,MAAQ,EAAE,MAAQ,EAAE,MAAQ,EAAE,MAAQ,EAAE,KAAM,EAEvF,EAAO,IAAQ,IAAA,IAAa,EAAM,EAAI,EAAQ,MAAM,EAAG,CAAG,EAAI,EAC9D,EAAM,IAAQ,IAAA,IAAa,EAAM,EAAI,EAAQ,MAAM,CAAG,EAAI,CAAC,EAE3D,EACF,IAAe,IAAA,IAAa,EAAI,OAAS,EACnC,CACI,GAAG,EACH,CACI,MAAO,EACP,MAAO,EAAI,QAAQ,EAAK,IAAS,EAAM,EAAK,MAAO,CAAC,CACxD,CACJ,EACA,IAAe,IAAA,IAAa,EAAI,SAAW,EACzC,CAAC,GAAG,EAAM,GAAG,CAAG,EAChB,EAEN,EAAU,EAAM,QAAQ,EAAM,IAAS,KAAK,IAAI,EAAM,EAAK,KAAK,EAAG,CAAC,EAE1E,OAAO,EAAM,KAAK,EAAM,KAAW,CAC/B,GAAG,EACH,QACA,WAAY,EAAA,UAAU,KAAK,IAAI,EAAK,MAAO,CAAC,EAAG,CAAK,EACpD,MAAO,EAAA,UAAU,KAAK,IAAI,EAAK,MAAO,CAAC,EAAG,CAAO,CACrD,EAAE,CACN"}
@@ -0,0 +1,18 @@
1
+ import { percentOf as e } from "../../utils/numbers.js";
2
+ //#region src/components/BarList/bar-list-model.ts
3
+ function t(t, n, r, i) {
4
+ let a = t.filter((e) => Number.isFinite(e.value)), o = a.reduce((e, t) => e + Math.max(t.value, 0), 0), s = n === "none" ? [...a] : [...a].sort((e, t) => n === "asc" ? e.value - t.value : t.value - e.value), c = r !== void 0 && r > 0 ? s.slice(0, r) : s, l = r !== void 0 && r > 0 ? s.slice(r) : [], u = i !== void 0 && l.length > 1 ? [...c, {
5
+ label: i,
6
+ value: l.reduce((e, t) => e + t.value, 0)
7
+ }] : i !== void 0 && l.length === 1 ? [...c, ...l] : c, d = u.reduce((e, t) => Math.max(e, t.value), 0);
8
+ return u.map((t, n) => ({
9
+ ...t,
10
+ index: n,
11
+ percentage: e(Math.max(t.value, 0), o),
12
+ width: e(Math.max(t.value, 0), d)
13
+ }));
14
+ }
15
+ //#endregion
16
+ export { t as buildBarListRows };
17
+
18
+ //# sourceMappingURL=bar-list-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bar-list-model.js","names":[],"sources":["../../../src/components/BarList/bar-list-model.ts"],"sourcesContent":["// The arithmetic behind BarList, kept out of the component so the decisions that\n// are easy to get wrong — an empty total, a negative value, what \"100% wide\"\n// means — are testable without rendering anything.\n\nimport { percentOf } from \"@/utils/numbers\";\n\n/** One entry of a {@link BarList}. */\nexport interface BarListItem {\n /** What the row is called. */\n label: string;\n /** The measured amount. */\n value: number;\n /** Override the bar colour. Defaults to the chart series token for its position. */\n color?: string;\n}\n\n/** How the list orders itself before drawing. */\nexport type BarListSort = \"desc\" | \"asc\" | \"none\";\n\n/** One row, with everything the component needs to draw it. */\nexport interface BarListRow extends BarListItem {\n /** Share of the total, 0–100. */\n percentage: number;\n /** Bar width relative to the largest row, 0–100. */\n width: number;\n /** Position in the original palette cycle. */\n index: number;\n}\n\n/**\n * Order, truncate and measure the rows.\n *\n * Two different numbers come out of this, and conflating them is the classic bug\n * in a hand-written bar list:\n *\n * - `percentage` is the row's share of the **total**, which is what the label\n * claims when it reads \"32%\".\n * - `width` is relative to the **largest** row, so the biggest bar fills the\n * track. Scaling width by the total instead leaves every bar short in a list of\n * many small values, and the chart stops being readable exactly when it has the\n * most rows.\n *\n * The total counts positive values only. A negative amount draws no bar (a bar of\n * negative width does not exist) and reports 0%, but its number is still shown —\n * hiding the row would be worse than showing an odd one.\n *\n * @param items - The rows as given.\n * @param sort - Ordering to apply. `\"none\"` keeps the caller's order.\n * @param max - Keep at most this many rows.\n * @param otherLabel - Aggregate what `max` cut into one row with this label.\n * @returns The rows to draw, in order.\n */\nexport function buildBarListRows(\n items: readonly BarListItem[],\n sort: BarListSort,\n max: number | undefined,\n otherLabel: string | undefined,\n): BarListRow[] {\n const usable = items.filter((item) => Number.isFinite(item.value));\n const total = usable.reduce((sum, item) => sum + Math.max(item.value, 0), 0);\n\n const ordered =\n sort === \"none\"\n ? [...usable]\n : [...usable].sort((a, b) => (sort === \"asc\" ? a.value - b.value : b.value - a.value));\n\n const kept = max !== undefined && max > 0 ? ordered.slice(0, max) : ordered;\n const cut = max !== undefined && max > 0 ? ordered.slice(max) : [];\n\n const shown: BarListItem[] =\n otherLabel !== undefined && cut.length > 1\n ? [\n ...kept,\n {\n label: otherLabel,\n value: cut.reduce((sum, item) => sum + item.value, 0),\n },\n ]\n : otherLabel !== undefined && cut.length === 1\n ? [...kept, ...cut]\n : kept;\n\n const largest = shown.reduce((high, item) => Math.max(high, item.value), 0);\n\n return shown.map((item, index) => ({\n ...item,\n index,\n percentage: percentOf(Math.max(item.value, 0), total),\n width: percentOf(Math.max(item.value, 0), largest),\n }));\n}\n"],"mappings":";;AAoDA,SAAgB,EACZ,GACA,GACA,GACA,GACY;CACZ,IAAM,IAAS,EAAM,QAAQ,MAAS,OAAO,SAAS,EAAK,KAAK,CAAC,GAC3D,IAAQ,EAAO,QAAQ,GAAK,MAAS,IAAM,KAAK,IAAI,EAAK,OAAO,CAAC,GAAG,CAAC,GAErE,IACF,MAAS,SACH,CAAC,GAAG,CAAM,IACV,CAAC,GAAG,CAAM,CAAC,CAAC,MAAM,GAAG,MAAO,MAAS,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAM,GAEvF,IAAO,MAAQ,KAAA,KAAa,IAAM,IAAI,EAAQ,MAAM,GAAG,CAAG,IAAI,GAC9D,IAAM,MAAQ,KAAA,KAAa,IAAM,IAAI,EAAQ,MAAM,CAAG,IAAI,CAAC,GAE3D,IACF,MAAe,KAAA,KAAa,EAAI,SAAS,IACnC,CACI,GAAG,GACH;EACI,OAAO;EACP,OAAO,EAAI,QAAQ,GAAK,MAAS,IAAM,EAAK,OAAO,CAAC;CACxD,CACJ,IACA,MAAe,KAAA,KAAa,EAAI,WAAW,IACzC,CAAC,GAAG,GAAM,GAAG,CAAG,IAChB,GAEN,IAAU,EAAM,QAAQ,GAAM,MAAS,KAAK,IAAI,GAAM,EAAK,KAAK,GAAG,CAAC;CAE1E,OAAO,EAAM,KAAK,GAAM,OAAW;EAC/B,GAAG;EACH;EACA,YAAY,EAAU,KAAK,IAAI,EAAK,OAAO,CAAC,GAAG,CAAK;EACpD,OAAO,EAAU,KAAK,IAAI,EAAK,OAAO,CAAC,GAAG,CAAO;CACrD,EAAE;AACN"}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-pagination.cjs"),n=require("../../hooks/use-announce.cjs"),r=require("../Pagination/Pagination.cjs"),i=require("../SearchBar/SearchBar.cjs"),a=require("../Table/Table.cjs"),o=require("../../utils/compare-values.cjs"),s=require("./DataTable.module.cjs"),c=require("./EditableCell.cjs"),l=require("./edit-labels.cjs");let u=require("react"),d=require("react/jsx-runtime");function f(e,t){return`${String(e)}::${String(t)}`}function p(e){return typeof e.header==`string`?e.header:String(e.key)}function m({data:m,columns:h,pageSize:g=10,searchable:_=!1,searchKeys:v,initialSort:ee,rowKey:y=(e,t)=>t,emptyMessage:b,onCellChange:x,editLabels:S,className:C,...te}){let[w,ne]=(0,u.useState)(``),[T,E]=(0,u.useState)(ee??null),{page:D,setPage:O}=t.usePagination(1,g),k=n.useAnnounce(),[A,j]=(0,u.useState)(null),[M,N]=(0,u.useState)(null),[P,F]=(0,u.useState)({}),[I,L]=(0,u.useState)({}),[R,z]=(0,u.useState)({}),B=(0,u.useMemo)(()=>({...l.DEFAULT_EDIT_LABELS,...S}),[S]),V=x!==void 0&&h.some(e=>e.editable),H=(0,u.useMemo)(()=>v&&v.length>0?v:h.filter(e=>{let t=m.find(t=>t[e.key]!=null),n=t?t[e.key]:void 0;return typeof n==`string`||typeof n==`number`}).map(e=>e.key),[v,h,m]),U=(0,u.useMemo)(()=>{let e=w.trim().toLowerCase();return!e||!_?m:m.filter(t=>H.some(n=>{let r=t[n];return r!=null&&String(r).toLowerCase().includes(e)}))},[m,w,_,H]),W=(0,u.useMemo)(()=>{if(!T)return U;let e=T.direction===`asc`?1:-1;return[...U].sort((t,n)=>o.compareValues(t[T.key],n[T.key])*e)},[U,T]),G=Math.max(1,Math.ceil(W.length/g));(0,u.useEffect)(()=>{D>G&&O(G)},[D,G,O]);let K=Math.min(D,G),q=(0,u.useMemo)(()=>{let e=(K-1)*g;return W.slice(e,e+g)},[W,K,g]);function re(e){E(t=>!t||t.key!==e?{key:e,direction:`asc`}:t.direction===`asc`?{key:e,direction:`desc`}:null)}let J=(0,u.useCallback)(e=>(K-1)*g+e,[K,g]),Y=(0,u.useMemo)(()=>{if(!V)return[];let e=[];return q.forEach((t,n)=>{let r=y(t,J(n));for(let t of h)t.editable&&e.push(f(r,t.key))}),e},[V,q,h,J]),X=(0,u.useCallback)((e,t,n)=>n in P?P[n]:e[t.key],[P]),Z=(0,u.useCallback)(async(e,t,n)=>{z(t=>({...t,[e]:!0}));try{await x?.(n),k(B.saved(p(t)))}catch(n){F(t=>{let n={...t};return delete n[e],n});let r=n instanceof Error&&n.message?n.message:B.saveFailed(p(t));L(t=>({...t,[e]:r}))}finally{z(t=>{let n={...t};return delete n[e],n})}},[x,k,B]),Q=(0,u.useCallback)((e,t)=>{if(t===`none`){j(null),N(e);return}let n=Y.indexOf(e),r=Y[n+(t===`next`?1:-1)];if(r===void 0){j(null),N(e);return}j(r),N(null)},[Y]),$=(0,u.useCallback)((e,t,n,r,i,a)=>{let o=X(e,t,n);if(i===(t.formatEdit?t.formatEdit(e):String(o??``))){Q(n,a);return}let s=t.parse?t.parse(i,e):t.editorType===`number`?Number(i):i.trim(),c=t.validate?.(s,e)??null;if(c){L(e=>({...e,[n]:c}));return}L(e=>{let t={...e};return delete t[n],t}),F(e=>({...e,[n]:s})),Q(n,a),Z(n,t,{row:e,key:t.key,value:s,previous:o,rowIndex:J(r)})},[X,Q,Z,J]),ie=(0,u.useMemo)(()=>h.map(e=>{let t=T?.key===e.key?T?.direction===`asc`?` ▲`:` ▼`:``,n=e.sortable?(0,d.jsxs)(`button`,{type:`button`,className:s.default.sortButton,onClick:()=>re(e.key),"aria-label":`Ordenar por ${p(e)}`,children:[e.header,(0,d.jsx)(`span`,{className:s.default.sortIndicator,"aria-hidden":!0,children:t})]}):e.header,r=t=>e.render?e.render(t):t[e.key]??null,i=(t,n)=>{let r={...t,[e.key]:n};return e.render?e.render(r):r[e.key]??null},a=V&&e.editable===!0;return{key:String(e.key),header:n,align:e.align,priority:e.priority,width:e.width,render:a?(t,n)=>{let r=f(y(t,J(n)),e.key),a=X(t,e,r),o=e.formatEdit?e.formatEdit(t):String(a??``);return(0,d.jsx)(c.EditableCell,{text:o,columnLabel:p(e),rowNumber:n+1,inputType:e.editorType??`text`,editing:A===r,refocus:M===r,saving:R[r]===!0,error:I[r]??null,errorId:`tempest-cell-error-${r.replace(/[^\w-]/g,`_`)}`,labels:B,onOpen:()=>{j(r),N(null)},onCommit:(i,a)=>$(t,e,r,n,i,a),onCancel:()=>{j(null),N(r),L(e=>{let t={...e};return delete t[r],t})},children:i(t,a)})}:r}}),[h,T,V,A,M,R,I,B,P,J,$]);return(0,d.jsxs)(`div`,{className:e.cn(s.default.wrapper,C),...te,children:[_&&(0,d.jsx)(i.SearchBar,{value:w,onChange:e=>{ne(e),O(1)},wrapperClassName:s.default.search}),(0,d.jsx)(a.Table,{columns:ie,data:q,rowKey:(e,t)=>y(e,J(t)),emptyMessage:b}),G>1&&(0,d.jsx)(r.Pagination,{page:K,totalPages:G,onPageChange:O,totalItems:W.length})]})}exports.DataTable=m;
1
+ const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-pagination.cjs"),n=require("../../hooks/use-announce.cjs"),r=require("../Pagination/Pagination.cjs"),i=require("../SearchBar/SearchBar.cjs"),a=require("../Table/Table.cjs"),o=require("../../utils/compare-values.cjs"),s=require("./DataTable.module.cjs"),c=require("./EditableCell.cjs"),ee=require("./LoadingRows.cjs"),te=require("./use-dev-warnings.cjs"),ne=require("./edit-labels.cjs");let l=require("react"),u=require("react/jsx-runtime");function d(e,t){return`${String(e)}::${String(t)}`}function f(e){return typeof e.header==`string`?e.header:String(e.key)}function p({data:p,columns:m,pageSize:h=10,searchable:g=!1,searchKeys:_,initialSort:re,rowKey:v=(e,t)=>t,emptyMessage:ie,onCellChange:y,editLabels:b,totalItems:ae,page:x,onPageChange:S,manualSort:oe,onSortChange:se,manualSearch:ce,onSearchChange:le,loading:C=!1,className:ue,...de}){let[w,fe]=(0,l.useState)(``),[T,pe]=(0,l.useState)(re??null),{page:me,setPage:E}=t.usePagination(1,h),D=n.useAnnounce(),O=ae!==void 0,k=oe??O,A=ce??O,j=x??me,M=(0,l.useCallback)(e=>{x===void 0&&E(e),S?.(e)},[x,E,S]);te.useDevWarnings({serverMode:O,controlledPage:x,onPageChange:S,sortIsManual:k,onSortChange:se});let[N,P]=(0,l.useState)(null),[F,I]=(0,l.useState)(null),[L,R]=(0,l.useState)({}),[z,B]=(0,l.useState)({}),[V,H]=(0,l.useState)({}),U=(0,l.useMemo)(()=>({...ne.DEFAULT_EDIT_LABELS,...b}),[b]),W=y!==void 0&&m.some(e=>e.editable),he=(0,l.useMemo)(()=>_&&_.length>0?_:m.filter(e=>{let t=p.find(t=>t[e.key]!=null),n=t?t[e.key]:void 0;return typeof n==`string`||typeof n==`number`}).map(e=>e.key),[_,m,p]),G=(0,l.useMemo)(()=>{let e=w.trim().toLowerCase();return!e||!g||A?p:p.filter(t=>he.some(n=>{let r=t[n];return r!=null&&String(r).toLowerCase().includes(e)}))},[p,w,g,A,he]),K=(0,l.useMemo)(()=>{if(!T||k)return G;let e=T.direction===`asc`?1:-1;return[...G].sort((t,n)=>o.compareValues(t[T.key],n[T.key])*e)},[G,T,k]),ge=ae??K.length,q=Math.max(1,Math.ceil(ge/h));(0,l.useEffect)(()=>{!O&&j>q&&M(q)},[O,j,q,M]);let J=O?j:Math.min(j,q),Y=(0,l.useMemo)(()=>{if(O)return K;let e=(J-1)*h;return K.slice(e,e+h)},[O,K,J,h]);function _e(e){let t=T,n=!t||t.key!==e?{key:e,direction:`asc`}:t.direction===`asc`?{key:e,direction:`desc`}:null;pe(n),se?.(n)}let X=(0,l.useCallback)(e=>(J-1)*h+e,[J,h]),Z=(0,l.useMemo)(()=>{if(!W)return[];let e=[];return Y.forEach((t,n)=>{let r=v(t,X(n));for(let t of m)t.editable&&e.push(d(r,t.key))}),e},[W,Y,m,X]),Q=(0,l.useCallback)((e,t,n)=>n in L?L[n]:e[t.key],[L]),ve=(0,l.useCallback)(async(e,t,n)=>{H(t=>({...t,[e]:!0}));try{await y?.(n),D(U.saved(f(t)))}catch(n){R(t=>{let n={...t};return delete n[e],n});let r=n instanceof Error&&n.message?n.message:U.saveFailed(f(t));B(t=>({...t,[e]:r}))}finally{H(t=>{let n={...t};return delete n[e],n})}},[y,D,U]),$=(0,l.useCallback)((e,t)=>{if(t===`none`){P(null),I(e);return}let n=Z.indexOf(e),r=Z[n+(t===`next`?1:-1)];if(r===void 0){P(null),I(e);return}P(r),I(null)},[Z]),ye=(0,l.useCallback)((e,t,n,r,i,a)=>{let o=Q(e,t,n);if(i===(t.formatEdit?t.formatEdit(e):String(o??``))){$(n,a);return}let s=t.parse?t.parse(i,e):t.editorType===`number`?Number(i):i.trim(),c=t.validate?.(s,e)??null;if(c){B(e=>({...e,[n]:c}));return}B(e=>{let t={...e};return delete t[n],t}),R(e=>({...e,[n]:s})),$(n,a),ve(n,t,{row:e,key:t.key,value:s,previous:o,rowIndex:X(r)})},[Q,$,ve,X]),be=(0,l.useMemo)(()=>m.map(e=>{let t=T?.key===e.key?T?.direction===`asc`?` ▲`:` ▼`:``,n=e.sortable?(0,u.jsxs)(`button`,{type:`button`,className:s.default.sortButton,onClick:()=>_e(e.key),"aria-label":`Ordenar por ${f(e)}`,children:[e.header,(0,u.jsx)(`span`,{className:s.default.sortIndicator,"aria-hidden":!0,children:t})]}):e.header,r=t=>e.render?e.render(t):t[e.key]??null,i=(t,n)=>{let r={...t,[e.key]:n};return e.render?e.render(r):r[e.key]??null},a=W&&e.editable===!0;return{key:String(e.key),header:n,align:e.align,priority:e.priority,width:e.width,render:a?(t,n)=>{let r=d(v(t,X(n)),e.key),a=Q(t,e,r),o=e.formatEdit?e.formatEdit(t):String(a??``);return(0,u.jsx)(c.EditableCell,{text:o,columnLabel:f(e),rowNumber:n+1,inputType:e.editorType??`text`,editing:N===r,refocus:F===r,saving:V[r]===!0,error:z[r]??null,errorId:`tempest-cell-error-${r.replace(/[^\w-]/g,`_`)}`,labels:U,onOpen:()=>{P(r),I(null)},onCommit:(i,a)=>ye(t,e,r,n,i,a),onCancel:()=>{P(null),I(r),B(e=>{let t={...e};return delete t[r],t})},children:i(t,a)})}:r}}),[m,T,W,N,F,V,z,U,L,X,ye]),xe=C&&Y.length===0;return(0,u.jsxs)(`div`,{className:e.cn(s.default.wrapper,ue),...de,children:[g&&(0,u.jsx)(i.SearchBar,{value:w,onChange:e=>{fe(e),M(1),le?.(e)},wrapperClassName:s.default.search}),(0,u.jsx)(`div`,{className:e.cn(C&&!xe&&s.default.pending),"aria-busy":C||void 0,"data-testid":`tempest-datatable-body`,children:xe?(0,u.jsx)(ee.LoadingRows,{columns:be.length,rows:Math.min(h,8)}):(0,u.jsx)(a.Table,{columns:be,data:Y,rowKey:(e,t)=>v(e,X(t)),emptyMessage:ie})}),q>1&&(0,u.jsx)(r.Pagination,{page:J,totalPages:q,onPageChange:M,totalItems:ge})]})}exports.DataTable=p;
2
2
  //# sourceMappingURL=DataTable.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.cjs","names":[],"sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the table does four jobs\n * a caller turns on independently — paging (pageSize), search (searchable,\n * searchKeys), sort (initialSort) and inline edit (onCellChange, editLabels) — over\n * one row model (data, columns, rowKey, emptyMessage). The body is long because\n * those four share the derived-rows pipeline: filter, then sort, then page, then map\n * to cells, in that order and off the same memo.\n */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { compareValues } from \"@/utils/compare-values\";\nimport { usePagination } from \"@/hooks\";\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { Table, type TableAlign, type TableColumn, type TablePriority } from \"../Table\";\nimport { Pagination } from \"../Pagination\";\nimport { SearchBar } from \"../SearchBar\";\nimport { EditableCell } from \"./EditableCell\";\nimport { DEFAULT_EDIT_LABELS, type CellCommitMove, type DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort<T> {\n key: keyof T;\n direction: SortDirection;\n}\n\n/** Input types an editable column can use. */\nexport type DataTableEditorType = \"text\" | \"number\" | \"date\" | \"email\" | \"tel\" | \"url\";\n\n/** One accepted cell edit, handed to `onCellChange`. */\nexport interface DataTableCellChange<T> {\n /** The row as it was before the edit. */\n row: T;\n /** Which column changed. */\n key: keyof T;\n /** The parsed new value. */\n value: unknown;\n /** The value that was displayed before the edit. */\n previous: unknown;\n /** Index of the row in the full `data` array. */\n rowIndex: number;\n}\n\n/**\n * Column definition for {@link DataTable}. Extends the headless {@link Table}\n * column shape with a typed `key`, opt-in sorting, opt-in inline editing, and the\n * visual options that are forwarded to the underlying Table cell.\n */\nexport interface DataTableColumn<T> {\n /** Property of the row this column reads from. Doubles as the cell key. */\n key: keyof T;\n /** Column heading. */\n header: ReactNode;\n /** Custom cell renderer. Defaults to `String(row[key])`. */\n render?: (row: T) => ReactNode;\n /** Enable click-to-sort on this column's header. */\n sortable?: boolean;\n /** Text alignment forwarded to the Table cell. */\n align?: TableAlign;\n /** Responsive visibility priority forwarded to the Table cell. */\n priority?: TablePriority;\n /** Fixed column width forwarded to the Table cell. */\n width?: string | number;\n /**\n * Let cells in this column be edited in place. Requires `onCellChange` on the\n * table; without it the column stays read-only.\n */\n editable?: boolean;\n /** Editor input type. Default `\"text\"`. */\n editorType?: DataTableEditorType;\n /** Text the editor opens with. Defaults to `String(value ?? \"\")`. */\n formatEdit?: (row: T) => string;\n /**\n * Turn the typed string into the stored value. Defaults to the trimmed string,\n * or `Number(raw)` when `editorType` is `\"number\"`.\n */\n parse?: (raw: string, row: T) => unknown;\n /** Return a message to reject the edit, or `null` to accept it. */\n validate?: (value: unknown, row: T) => string | null;\n}\n\nexport interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement> {\n /** Full, unfiltered dataset. Sorting/filtering/pagination happen client-side. */\n data: T[];\n /** Column definitions. */\n columns: DataTableColumn<T>[];\n /** Rows per page. Default 10. */\n pageSize?: number;\n /** Render a search input above the table. Default false. */\n searchable?: boolean;\n /**\n * Keys to match the search term against. When omitted, every column whose\n * value is a string or number is searched.\n */\n searchKeys?: (keyof T)[];\n /** Initial sort applied before any header interaction. */\n initialSort?: DataTableSort<T>;\n /** Stable key extractor for rows. Defaults to the row index. */\n rowKey?: (row: T, index: number) => string | number;\n /** Content shown when no rows match. */\n emptyMessage?: ReactNode;\n /**\n * Persist an accepted cell edit. Return a promise: while it is pending the cell\n * already shows the new value, and a rejection rolls that back and surfaces the\n * error in the cell. Without this prop no column is editable.\n */\n onCellChange?: (change: DataTableCellChange<T>) => void | Promise<void>;\n /** Override the PT-BR copy of the editing affordances. */\n editLabels?: Partial<DataTableEditLabels>;\n}\n\n/** Identity of one cell, stable across re-renders and pagination. */\nfunction cellId(rowKeyValue: string | number, columnKey: PropertyKey): string {\n return `${String(rowKeyValue)}::${String(columnKey)}`;\n}\n\nfunction headerText<T>(column: DataTableColumn<T>): string {\n return typeof column.header === \"string\" ? column.header : String(column.key);\n}\n\n/**\n * Stateful, headless data table built on top of {@link Table}. Adds\n * client-side searching, click-to-sort columns, pagination and opt-in inline\n * editing while delegating all table markup to the underlying Table component.\n *\n * - Clicking a sortable header cycles asc → desc → unsorted.\n * - Search matches a case-insensitive substring across `searchKeys`\n * (or every string/number column when not provided).\n * - Pagination is hidden when the result fits on a single page.\n * - A column with `editable` renders a button that opens an inline editor;\n * `Enter` commits, `Escape` discards, `Tab` walks to the next editable cell.\n *\n * Editing is strictly opt-in: with no `editable` column (or no `onCellChange`) the\n * rendered markup is byte-for-byte what it was before the feature existed, which\n * matters because the component is published.\n *\n * ## Optimistic, with a visible rollback\n *\n * An accepted edit is shown immediately and `onCellChange` runs in the background.\n * If it rejects, the cell returns to the old value **and** shows the reason as a\n * `role=\"alert\"` tied to the cell. A silent revert is worse than no optimistic\n * update at all: the user watched their edit appear and has no reason to doubt it.\n *\n * The header memo depends on `columns`, `sort` and the editing state only:\n * `toggleSort` and the commit callbacks are recreated each render but always close\n * over the same setters, so including them would rebuild every header on every\n * render without changing behaviour. That is why `exhaustive-deps` is silenced on\n * that dependency array.\n */\nexport function DataTable<T>({\n data,\n columns,\n pageSize = 10,\n searchable = false,\n searchKeys,\n initialSort,\n rowKey = (_row, index) => index,\n emptyMessage,\n onCellChange,\n editLabels,\n className,\n ...rest\n}: DataTableProps<T>) {\n const [search, setSearch] = useState<string>(\"\");\n const [sort, setSort] = useState<DataTableSort<T> | null>(initialSort ?? null);\n const { page, setPage } = usePagination(1, pageSize);\n const announce = useAnnounce();\n\n const [editing, setEditing] = useState<string | null>(null);\n const [refocus, setRefocus] = useState<string | null>(null);\n const [overrides, setOverrides] = useState<Record<string, unknown>>({});\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [saving, setSaving] = useState<Record<string, boolean>>({});\n\n const labels = useMemo<DataTableEditLabels>(\n () => ({ ...DEFAULT_EDIT_LABELS, ...editLabels }),\n [editLabels],\n );\n const editingEnabled = onCellChange !== undefined && columns.some((column) => column.editable);\n\n const effectiveSearchKeys = useMemo<(keyof T)[]>(() => {\n if (searchKeys && searchKeys.length > 0) return searchKeys;\n return columns\n .filter((column) => {\n const sample = data.find((row) => row[column.key] != null);\n const value = sample ? sample[column.key] : undefined;\n return typeof value === \"string\" || typeof value === \"number\";\n })\n .map((column) => column.key);\n }, [searchKeys, columns, data]);\n\n const filtered = useMemo<T[]>(() => {\n const term = search.trim().toLowerCase();\n if (!term || !searchable) return data;\n return data.filter((row) =>\n effectiveSearchKeys.some((key) => {\n const value = row[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [data, search, searchable, effectiveSearchKeys]);\n\n const sorted = useMemo<T[]>(() => {\n if (!sort) return filtered;\n const factor = sort.direction === \"asc\" ? 1 : -1;\n return [...filtered].sort((a, b) => compareValues(a[sort.key], b[sort.key]) * factor);\n }, [filtered, sort]);\n\n const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));\n\n // Clamp the current page when the dataset shrinks (e.g. after filtering).\n useEffect(() => {\n if (page > totalPages) setPage(totalPages);\n }, [page, totalPages, setPage]);\n\n const safePage = Math.min(page, totalPages);\n const pageRows = useMemo<T[]>(() => {\n const start = (safePage - 1) * pageSize;\n return sorted.slice(start, start + pageSize);\n }, [sorted, safePage, pageSize]);\n\n function toggleSort(key: keyof T): void {\n setSort((current) => {\n if (!current || current.key !== key) return { key, direction: \"asc\" };\n if (current.direction === \"asc\") return { key, direction: \"desc\" };\n return null;\n });\n }\n\n const absoluteIndex = useCallback(\n (pageIndex: number) => (safePage - 1) * pageSize + pageIndex,\n [safePage, pageSize],\n );\n\n /**\n * Every editable cell on the page, row-major — the order `Tab` walks.\n *\n * Row-major and not column-major because a row is the record a user is\n * correcting; walking down a column would make them re-find their place on\n * every keystroke.\n */\n const editableCellIds = useMemo<string[]>(() => {\n if (!editingEnabled) return [];\n const ids: string[] = [];\n pageRows.forEach((row, index) => {\n const key = rowKey(row, absoluteIndex(index));\n for (const column of columns) {\n if (column.editable) ids.push(cellId(key, column.key));\n }\n });\n return ids;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editingEnabled, pageRows, columns, absoluteIndex]);\n\n const displayed = useCallback(\n (row: T, column: DataTableColumn<T>, id: string): unknown =>\n id in overrides ? overrides[id] : row[column.key],\n [overrides],\n );\n\n /**\n * Run the caller's `onCellChange` behind the optimistic update.\n *\n * Only the *success* is announced. The failure already renders as a\n * `role=\"alert\"` inside the cell, which screen readers read on insertion —\n * announcing it again from the shared region would read it twice and put the\n * same text in the document twice.\n */\n const persist = useCallback(\n async (id: string, column: DataTableColumn<T>, change: DataTableCellChange<T>) => {\n setSaving((current) => ({ ...current, [id]: true }));\n try {\n await onCellChange?.(change);\n announce(labels.saved(headerText(column)));\n } catch (error) {\n setOverrides((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n const message =\n error instanceof Error && error.message\n ? error.message\n : labels.saveFailed(headerText(column));\n setErrors((current) => ({ ...current, [id]: message }));\n } finally {\n setSaving((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }\n },\n [onCellChange, announce, labels],\n );\n\n const moveFrom = useCallback(\n (id: string, move: CellCommitMove): void => {\n if (move === \"none\") {\n setEditing(null);\n setRefocus(id);\n return;\n }\n const index = editableCellIds.indexOf(id);\n const target = editableCellIds[index + (move === \"next\" ? 1 : -1)];\n if (target === undefined) {\n setEditing(null);\n setRefocus(id);\n return;\n }\n setEditing(target);\n setRefocus(null);\n },\n [editableCellIds],\n );\n\n /**\n * Parse, validate, stage optimistically, then save in the background.\n *\n * Validation runs before anything is staged, and a rejection leaves the editor\n * open with the message attached to the input — the user has to be able to fix\n * what they typed without retyping it.\n */\n const commit = useCallback(\n (\n row: T,\n column: DataTableColumn<T>,\n id: string,\n pageIndex: number,\n raw: string,\n move: CellCommitMove,\n ): void => {\n const previous = displayed(row, column, id);\n const currentText = column.formatEdit ? column.formatEdit(row) : String(previous ?? \"\");\n if (raw === currentText) {\n moveFrom(id, move);\n return;\n }\n\n const value = column.parse\n ? column.parse(raw, row)\n : column.editorType === \"number\"\n ? Number(raw)\n : raw.trim();\n\n const invalid = column.validate?.(value, row) ?? null;\n if (invalid) {\n setErrors((current) => ({ ...current, [id]: invalid }));\n return;\n }\n\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n setOverrides((current) => ({ ...current, [id]: value }));\n moveFrom(id, move);\n void persist(id, column, {\n row,\n key: column.key,\n value,\n previous,\n rowIndex: absoluteIndex(pageIndex),\n });\n },\n [displayed, moveFrom, persist, absoluteIndex],\n );\n\n const tableColumns = useMemo<TableColumn<T>[]>(\n () =>\n columns.map((column) => {\n const isSorted = sort?.key === column.key;\n const indicator = isSorted ? (sort?.direction === \"asc\" ? \" ▲\" : \" ▼\") : \"\";\n const header = column.sortable ? (\n <button\n type=\"button\"\n className={styles.sortButton}\n onClick={() => toggleSort(column.key)}\n aria-label={`Ordenar por ${headerText(column)}`}\n >\n {column.header}\n <span className={styles.sortIndicator} aria-hidden>\n {indicator}\n </span>\n </button>\n ) : (\n column.header\n );\n\n const plainCell = (row: T): ReactNode => {\n if (column.render) return column.render(row);\n return (row[column.key] as ReactNode) ?? null;\n };\n\n /**\n * Render an editable cell's content against the optimistic value.\n *\n * The row is shallow-patched rather than the value passed alongside it,\n * because a column with a custom `render` (a `<Money>`, a badge) reads\n * the row — handing it the stale row would show the old number under a\n * cell the user just changed.\n */\n const patchedCell = (row: T, value: unknown): ReactNode => {\n const patched = { ...row, [column.key]: value } as T;\n if (column.render) return column.render(patched);\n return (patched[column.key] as ReactNode) ?? null;\n };\n\n const editable = editingEnabled && column.editable === true;\n\n return {\n key: String(column.key),\n header,\n align: column.align,\n priority: column.priority,\n width: column.width,\n render: editable\n ? (row: T, index: number) => {\n const id = cellId(rowKey(row, absoluteIndex(index)), column.key);\n const value = displayed(row, column, id);\n const text = column.formatEdit\n ? column.formatEdit(row)\n : String(value ?? \"\");\n return (\n <EditableCell\n text={text}\n columnLabel={headerText(column)}\n rowNumber={index + 1}\n inputType={column.editorType ?? \"text\"}\n editing={editing === id}\n refocus={refocus === id}\n saving={saving[id] === true}\n error={errors[id] ?? null}\n errorId={`tempest-cell-error-${id.replace(/[^\\w-]/g, \"_\")}`}\n labels={labels}\n onOpen={() => {\n setEditing(id);\n setRefocus(null);\n }}\n onCommit={(raw, move) =>\n commit(row, column, id, index, raw, move)\n }\n onCancel={() => {\n setEditing(null);\n setRefocus(id);\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }}\n >\n {patchedCell(row, value)}\n </EditableCell>\n );\n }\n : plainCell,\n };\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n columns,\n sort,\n editingEnabled,\n editing,\n refocus,\n saving,\n errors,\n labels,\n overrides,\n absoluteIndex,\n commit,\n ],\n );\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {searchable && (\n <SearchBar\n value={search}\n onChange={(value) => {\n setSearch(value);\n setPage(1);\n }}\n wrapperClassName={styles.search}\n />\n )}\n <Table\n columns={tableColumns}\n data={pageRows}\n rowKey={(row, index) => rowKey(row, absoluteIndex(index))}\n emptyMessage={emptyMessage}\n />\n {totalPages > 1 && (\n <Pagination\n page={safePage}\n totalPages={totalPages}\n onPageChange={setPage}\n totalItems={sorted.length}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DataTable.cjs","names":[],"sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the table does four jobs\n * a caller turns on independently — paging (pageSize), search (searchable,\n * searchKeys), sort (initialSort) and inline edit (onCellChange, editLabels) — over\n * one row model (data, columns, rowKey, emptyMessage). The body is long because\n * those four share the derived-rows pipeline: filter, then sort, then page, then map\n * to cells, in that order and off the same memo.\n */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { compareValues } from \"@/utils/compare-values\";\nimport { usePagination } from \"@/hooks\";\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { Table, type TableAlign, type TableColumn, type TablePriority } from \"../Table\";\nimport { Pagination } from \"../Pagination\";\nimport { SearchBar } from \"../SearchBar\";\nimport { EditableCell } from \"./EditableCell\";\nimport { LoadingRows } from \"./LoadingRows\";\nimport { useDevWarnings } from \"./use-dev-warnings\";\nimport { DEFAULT_EDIT_LABELS, type CellCommitMove, type DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort<T> {\n key: keyof T;\n direction: SortDirection;\n}\n\n/** Input types an editable column can use. */\nexport type DataTableEditorType = \"text\" | \"number\" | \"date\" | \"email\" | \"tel\" | \"url\";\n\n/** One accepted cell edit, handed to `onCellChange`. */\nexport interface DataTableCellChange<T> {\n /** The row as it was before the edit. */\n row: T;\n /** Which column changed. */\n key: keyof T;\n /** The parsed new value. */\n value: unknown;\n /** The value that was displayed before the edit. */\n previous: unknown;\n /** Index of the row in the full `data` array. */\n rowIndex: number;\n}\n\n/**\n * Column definition for {@link DataTable}. Extends the headless {@link Table}\n * column shape with a typed `key`, opt-in sorting, opt-in inline editing, and the\n * visual options that are forwarded to the underlying Table cell.\n */\nexport interface DataTableColumn<T> {\n /** Property of the row this column reads from. Doubles as the cell key. */\n key: keyof T;\n /** Column heading. */\n header: ReactNode;\n /** Custom cell renderer. Defaults to `String(row[key])`. */\n render?: (row: T) => ReactNode;\n /** Enable click-to-sort on this column's header. */\n sortable?: boolean;\n /** Text alignment forwarded to the Table cell. */\n align?: TableAlign;\n /** Responsive visibility priority forwarded to the Table cell. */\n priority?: TablePriority;\n /** Fixed column width forwarded to the Table cell. */\n width?: string | number;\n /**\n * Let cells in this column be edited in place. Requires `onCellChange` on the\n * table; without it the column stays read-only.\n */\n editable?: boolean;\n /** Editor input type. Default `\"text\"`. */\n editorType?: DataTableEditorType;\n /** Text the editor opens with. Defaults to `String(value ?? \"\")`. */\n formatEdit?: (row: T) => string;\n /**\n * Turn the typed string into the stored value. Defaults to the trimmed string,\n * or `Number(raw)` when `editorType` is `\"number\"`.\n */\n parse?: (raw: string, row: T) => unknown;\n /** Return a message to reject the edit, or `null` to accept it. */\n validate?: (value: unknown, row: T) => string | null;\n}\n\nexport interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement> {\n /**\n * The rows to work with.\n *\n * By default this is the **full** dataset and sorting, searching and paging\n * all happen in memory. Pass `totalItems` and it becomes the current page as\n * the server returned it, with those three delegated to the caller.\n */\n data: T[];\n /** Column definitions. */\n columns: DataTableColumn<T>[];\n /** Rows per page. Default 10. */\n pageSize?: number;\n /** Render a search input above the table. Default false. */\n searchable?: boolean;\n /**\n * Keys to match the search term against. When omitted, every column whose\n * value is a string or number is searched.\n */\n searchKeys?: (keyof T)[];\n /** Initial sort applied before any header interaction. */\n initialSort?: DataTableSort<T>;\n /** Stable key extractor for rows. Defaults to the row index. */\n rowKey?: (row: T, index: number) => string | number;\n /** Content shown when no rows match. */\n emptyMessage?: ReactNode;\n /**\n * Persist an accepted cell edit. Return a promise: while it is pending the cell\n * already shows the new value, and a rejection rolls that back and surfaces the\n * error in the cell. Without this prop no column is editable.\n */\n onCellChange?: (change: DataTableCellChange<T>) => void | Promise<void>;\n /** Override the PT-BR copy of the editing affordances. */\n editLabels?: Partial<DataTableEditLabels>;\n /**\n * Total row count across every page — the `total` of a paginated envelope.\n *\n * Passing it switches the table to **server mode**: `data` is read as the\n * current page, the page count comes from this number instead of\n * `data.length`, and sorting and searching are delegated to the caller\n * (see `manualSort` / `manualSearch`, which are implied here). Pair it with\n * `page` and `onPageChange`.\n */\n totalItems?: number;\n /** Current page, 1-based. Controlled — required in server mode. */\n page?: number;\n /** Called with the next page. Required whenever `page` is controlled. */\n onPageChange?: (page: number) => void;\n /**\n * Sorting is the caller's job: clicking a header reports through\n * `onSortChange` and the rows are left in the order they arrived.\n *\n * Implied by `totalItems`, because sorting the page in memory would sort\n * *that page only* while the header claims the whole table is ordered.\n */\n manualSort?: boolean;\n /** Called with the next sort state — `null` when the header cycles back to unsorted. */\n onSortChange?: (sort: DataTableSort<T> | null) => void;\n /**\n * Searching is the caller's job: typing reports through `onSearchChange` and\n * the rows are left as they arrived.\n *\n * Implied by `totalItems`. Filtering the current page would hide the rows\n * that do not match *on this page* and show nothing for a term that only\n * matches on page three — an empty table that looks like \"no results\".\n */\n manualSearch?: boolean;\n /** Called with the current search term (debouncing, if any, is the caller's). */\n onSearchChange?: (term: string) => void;\n /**\n * A fetch is in flight.\n *\n * With rows already on screen they stay put, dimmed and `aria-busy`, so the\n * page does not jump under the cursor between pages. With no rows yet it\n * renders placeholder lines at full height, which is a different statement\n * from `emptyMessage`: \"loading\" and \"there is nothing\" are not the same\n * screen.\n */\n loading?: boolean;\n}\n\n/** Identity of one cell, stable across re-renders and pagination. */\nfunction cellId(rowKeyValue: string | number, columnKey: PropertyKey): string {\n return `${String(rowKeyValue)}::${String(columnKey)}`;\n}\n\nfunction headerText<T>(column: DataTableColumn<T>): string {\n return typeof column.header === \"string\" ? column.header : String(column.key);\n}\n\n/**\n * Stateful, headless data table built on top of {@link Table}. Adds\n * client-side searching, click-to-sort columns, pagination and opt-in inline\n * editing while delegating all table markup to the underlying Table component.\n *\n * - Clicking a sortable header cycles asc → desc → unsorted.\n * - Search matches a case-insensitive substring across `searchKeys`\n * (or every string/number column when not provided).\n * - Pagination is hidden when the result fits on a single page.\n * - A column with `editable` renders a button that opens an inline editor;\n * `Enter` commits, `Escape` discards, `Tab` walks to the next editable cell.\n *\n * Editing is strictly opt-in: with no `editable` column (or no `onCellChange`) the\n * rendered markup is byte-for-byte what it was before the feature existed, which\n * matters because the component is published.\n *\n * ## Optimistic, with a visible rollback\n *\n * An accepted edit is shown immediately and `onCellChange` runs in the background.\n * If it rejects, the cell returns to the old value **and** shows the reason as a\n * `role=\"alert\"` tied to the cell. A silent revert is worse than no optimistic\n * update at all: the user watched their edit appear and has no reason to doubt it.\n *\n * The header memo depends on `columns`, `sort` and the editing state only:\n * `toggleSort` and the commit callbacks are recreated each render but always close\n * over the same setters, so including them would rebuild every header on every\n * render without changing behaviour. That is why `exhaustive-deps` is silenced on\n * that dependency array.\n */\nexport function DataTable<T>({\n data,\n columns,\n pageSize = 10,\n searchable = false,\n searchKeys,\n initialSort,\n rowKey = (_row, index) => index,\n emptyMessage,\n onCellChange,\n editLabels,\n totalItems,\n page: controlledPage,\n onPageChange,\n manualSort,\n onSortChange,\n manualSearch,\n onSearchChange,\n loading = false,\n className,\n ...rest\n}: DataTableProps<T>) {\n const [search, setSearch] = useState<string>(\"\");\n const [sort, setSort] = useState<DataTableSort<T> | null>(initialSort ?? null);\n const { page: internalPage, setPage: setInternalPage } = usePagination(1, pageSize);\n const announce = useAnnounce();\n\n const serverMode = totalItems !== undefined;\n const sortIsManual = manualSort ?? serverMode;\n const searchIsManual = manualSearch ?? serverMode;\n const page = controlledPage ?? internalPage;\n\n const setPage = useCallback(\n (next: number) => {\n if (controlledPage === undefined) setInternalPage(next);\n onPageChange?.(next);\n },\n [controlledPage, setInternalPage, onPageChange],\n );\n\n useDevWarnings({ serverMode, controlledPage, onPageChange, sortIsManual, onSortChange });\n\n const [editing, setEditing] = useState<string | null>(null);\n const [refocus, setRefocus] = useState<string | null>(null);\n const [overrides, setOverrides] = useState<Record<string, unknown>>({});\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [saving, setSaving] = useState<Record<string, boolean>>({});\n\n const labels = useMemo<DataTableEditLabels>(\n () => ({ ...DEFAULT_EDIT_LABELS, ...editLabels }),\n [editLabels],\n );\n const editingEnabled = onCellChange !== undefined && columns.some((column) => column.editable);\n\n const effectiveSearchKeys = useMemo<(keyof T)[]>(() => {\n if (searchKeys && searchKeys.length > 0) return searchKeys;\n return columns\n .filter((column) => {\n const sample = data.find((row) => row[column.key] != null);\n const value = sample ? sample[column.key] : undefined;\n return typeof value === \"string\" || typeof value === \"number\";\n })\n .map((column) => column.key);\n }, [searchKeys, columns, data]);\n\n const filtered = useMemo<T[]>(() => {\n const term = search.trim().toLowerCase();\n if (!term || !searchable || searchIsManual) return data;\n return data.filter((row) =>\n effectiveSearchKeys.some((key) => {\n const value = row[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [data, search, searchable, searchIsManual, effectiveSearchKeys]);\n\n const sorted = useMemo<T[]>(() => {\n if (!sort || sortIsManual) return filtered;\n const factor = sort.direction === \"asc\" ? 1 : -1;\n return [...filtered].sort((a, b) => compareValues(a[sort.key], b[sort.key]) * factor);\n }, [filtered, sort, sortIsManual]);\n\n const rowCount = totalItems ?? sorted.length;\n const totalPages = Math.max(1, Math.ceil(rowCount / pageSize));\n\n /**\n * Clamp the current page when the dataset shrinks (e.g. after filtering).\n *\n * Skipped in server mode: `page` belongs to the caller there, and a clamp\n * fired against a `totalItems` that has not caught up with the new filter\n * yet would send them a page they did not ask for, mid-fetch.\n */\n useEffect(() => {\n if (!serverMode && page > totalPages) setPage(totalPages);\n }, [serverMode, page, totalPages, setPage]);\n\n const safePage = serverMode ? page : Math.min(page, totalPages);\n const pageRows = useMemo<T[]>(() => {\n if (serverMode) return sorted;\n const start = (safePage - 1) * pageSize;\n return sorted.slice(start, start + pageSize);\n }, [serverMode, sorted, safePage, pageSize]);\n\n function toggleSort(key: keyof T): void {\n const current = sort;\n const next: DataTableSort<T> | null =\n !current || current.key !== key\n ? { key, direction: \"asc\" }\n : current.direction === \"asc\"\n ? { key, direction: \"desc\" }\n : null;\n setSort(next);\n onSortChange?.(next);\n }\n\n const absoluteIndex = useCallback(\n (pageIndex: number) => (safePage - 1) * pageSize + pageIndex,\n [safePage, pageSize],\n );\n\n /**\n * Every editable cell on the page, row-major — the order `Tab` walks.\n *\n * Row-major and not column-major because a row is the record a user is\n * correcting; walking down a column would make them re-find their place on\n * every keystroke.\n */\n const editableCellIds = useMemo<string[]>(() => {\n if (!editingEnabled) return [];\n const ids: string[] = [];\n pageRows.forEach((row, index) => {\n const key = rowKey(row, absoluteIndex(index));\n for (const column of columns) {\n if (column.editable) ids.push(cellId(key, column.key));\n }\n });\n return ids;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editingEnabled, pageRows, columns, absoluteIndex]);\n\n const displayed = useCallback(\n (row: T, column: DataTableColumn<T>, id: string): unknown =>\n id in overrides ? overrides[id] : row[column.key],\n [overrides],\n );\n\n /**\n * Run the caller's `onCellChange` behind the optimistic update.\n *\n * Only the *success* is announced. The failure already renders as a\n * `role=\"alert\"` inside the cell, which screen readers read on insertion —\n * announcing it again from the shared region would read it twice and put the\n * same text in the document twice.\n */\n const persist = useCallback(\n async (id: string, column: DataTableColumn<T>, change: DataTableCellChange<T>) => {\n setSaving((current) => ({ ...current, [id]: true }));\n try {\n await onCellChange?.(change);\n announce(labels.saved(headerText(column)));\n } catch (error) {\n setOverrides((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n const message =\n error instanceof Error && error.message\n ? error.message\n : labels.saveFailed(headerText(column));\n setErrors((current) => ({ ...current, [id]: message }));\n } finally {\n setSaving((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }\n },\n [onCellChange, announce, labels],\n );\n\n const moveFrom = useCallback(\n (id: string, move: CellCommitMove): void => {\n if (move === \"none\") {\n setEditing(null);\n setRefocus(id);\n return;\n }\n const index = editableCellIds.indexOf(id);\n const target = editableCellIds[index + (move === \"next\" ? 1 : -1)];\n if (target === undefined) {\n setEditing(null);\n setRefocus(id);\n return;\n }\n setEditing(target);\n setRefocus(null);\n },\n [editableCellIds],\n );\n\n /**\n * Parse, validate, stage optimistically, then save in the background.\n *\n * Validation runs before anything is staged, and a rejection leaves the editor\n * open with the message attached to the input — the user has to be able to fix\n * what they typed without retyping it.\n */\n const commit = useCallback(\n (\n row: T,\n column: DataTableColumn<T>,\n id: string,\n pageIndex: number,\n raw: string,\n move: CellCommitMove,\n ): void => {\n const previous = displayed(row, column, id);\n const currentText = column.formatEdit ? column.formatEdit(row) : String(previous ?? \"\");\n if (raw === currentText) {\n moveFrom(id, move);\n return;\n }\n\n const value = column.parse\n ? column.parse(raw, row)\n : column.editorType === \"number\"\n ? Number(raw)\n : raw.trim();\n\n const invalid = column.validate?.(value, row) ?? null;\n if (invalid) {\n setErrors((current) => ({ ...current, [id]: invalid }));\n return;\n }\n\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n setOverrides((current) => ({ ...current, [id]: value }));\n moveFrom(id, move);\n void persist(id, column, {\n row,\n key: column.key,\n value,\n previous,\n rowIndex: absoluteIndex(pageIndex),\n });\n },\n [displayed, moveFrom, persist, absoluteIndex],\n );\n\n const tableColumns = useMemo<TableColumn<T>[]>(\n () =>\n columns.map((column) => {\n const isSorted = sort?.key === column.key;\n const indicator = isSorted ? (sort?.direction === \"asc\" ? \" ▲\" : \" ▼\") : \"\";\n const header = column.sortable ? (\n <button\n type=\"button\"\n className={styles.sortButton}\n onClick={() => toggleSort(column.key)}\n aria-label={`Ordenar por ${headerText(column)}`}\n >\n {column.header}\n <span className={styles.sortIndicator} aria-hidden>\n {indicator}\n </span>\n </button>\n ) : (\n column.header\n );\n\n const plainCell = (row: T): ReactNode => {\n if (column.render) return column.render(row);\n return (row[column.key] as ReactNode) ?? null;\n };\n\n /**\n * Render an editable cell's content against the optimistic value.\n *\n * The row is shallow-patched rather than the value passed alongside it,\n * because a column with a custom `render` (a `<Money>`, a badge) reads\n * the row — handing it the stale row would show the old number under a\n * cell the user just changed.\n */\n const patchedCell = (row: T, value: unknown): ReactNode => {\n const patched = { ...row, [column.key]: value } as T;\n if (column.render) return column.render(patched);\n return (patched[column.key] as ReactNode) ?? null;\n };\n\n const editable = editingEnabled && column.editable === true;\n\n return {\n key: String(column.key),\n header,\n align: column.align,\n priority: column.priority,\n width: column.width,\n render: editable\n ? (row: T, index: number) => {\n const id = cellId(rowKey(row, absoluteIndex(index)), column.key);\n const value = displayed(row, column, id);\n const text = column.formatEdit\n ? column.formatEdit(row)\n : String(value ?? \"\");\n return (\n <EditableCell\n text={text}\n columnLabel={headerText(column)}\n rowNumber={index + 1}\n inputType={column.editorType ?? \"text\"}\n editing={editing === id}\n refocus={refocus === id}\n saving={saving[id] === true}\n error={errors[id] ?? null}\n errorId={`tempest-cell-error-${id.replace(/[^\\w-]/g, \"_\")}`}\n labels={labels}\n onOpen={() => {\n setEditing(id);\n setRefocus(null);\n }}\n onCommit={(raw, move) =>\n commit(row, column, id, index, raw, move)\n }\n onCancel={() => {\n setEditing(null);\n setRefocus(id);\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }}\n >\n {patchedCell(row, value)}\n </EditableCell>\n );\n }\n : plainCell,\n };\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n columns,\n sort,\n editingEnabled,\n editing,\n refocus,\n saving,\n errors,\n labels,\n overrides,\n absoluteIndex,\n commit,\n ],\n );\n\n const showSkeleton = loading && pageRows.length === 0;\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {searchable && (\n <SearchBar\n value={search}\n onChange={(value) => {\n setSearch(value);\n setPage(1);\n onSearchChange?.(value);\n }}\n wrapperClassName={styles.search}\n />\n )}\n <div\n className={cn(loading && !showSkeleton && styles.pending)}\n aria-busy={loading || undefined}\n data-testid=\"tempest-datatable-body\"\n >\n {showSkeleton ? (\n <LoadingRows columns={tableColumns.length} rows={Math.min(pageSize, 8)} />\n ) : (\n <Table\n columns={tableColumns}\n data={pageRows}\n rowKey={(row, index) => rowKey(row, absoluteIndex(index))}\n emptyMessage={emptyMessage}\n />\n )}\n </div>\n {totalPages > 1 && (\n <Pagination\n page={safePage}\n totalPages={totalPages}\n onPageChange={setPage}\n totalItems={rowCount}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}