@naeil/ui 0.11.0 → 0.13.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 (82) hide show
  1. package/CHANGELOG.md +38 -2
  2. package/DESIGN.md +13 -3
  3. package/README.md +13 -11
  4. package/THIRD_PARTY_NOTICES.md +4 -2
  5. package/dist/{chunk-KWEWR46R.js → chunk-5BXZPDHZ.js} +68 -8
  6. package/dist/{chunk-U3IZCFM4.js → chunk-A2DCQVTP.js} +1 -1
  7. package/dist/{chunk-KPE4KKYM.js → chunk-Y5LW3ADW.js} +24 -15
  8. package/dist/components/index.d.ts +3 -3
  9. package/dist/components/index.js +4 -4
  10. package/dist/components/naeil-footer.d.ts +2 -2
  11. package/dist/components/naeil-footer.js +1 -1
  12. package/dist/components/naeil-header.d.ts +23 -8
  13. package/dist/components/naeil-header.js +7 -5
  14. package/dist/components/ui/index.d.ts +22 -5
  15. package/dist/components/ui/index.js +181 -34
  16. package/dist/lib/ui-release.d.ts +1 -1
  17. package/dist/lib/ui-release.js +1 -1
  18. package/dist/lib/utils.js +1 -7
  19. package/dist/naeil-locale/checker.js +1 -1
  20. package/dist/naeil-locale/chrome.d.ts +25 -8
  21. package/dist/naeil-locale/chrome.js +91 -22
  22. package/dist/naeil-locale/{chunk-4U64K4J3.js → chunk-24IL53QO.js} +1 -1
  23. package/dist/naeil-locale/{chunk-U65OQHZY.js → chunk-WT6QXR7O.js} +14 -8
  24. package/dist/naeil-locale/client.js +45 -15
  25. package/dist/naeil-locale/index.js +1 -1
  26. package/dist/naeil-locale/messages.js +2 -2
  27. package/dist/naeil-locale/server.js +6 -6
  28. package/dist/{navigation-D8N1tMTu.d.ts → navigation-CXZapKac.d.ts} +3 -1
  29. package/dist/{site-footer-s113FZgC.d.ts → site-footer-C4jgkGDU.d.ts} +1 -1
  30. package/dist/source/components/naeil-header-core.d.ts +23 -8
  31. package/dist/source/components/naeil-header-core.js +66 -6
  32. package/dist/source/components/naeil-header.d.ts +6 -4
  33. package/dist/source/components/naeil-header.js +4 -2
  34. package/dist/source/components/typography.d.ts +1 -1
  35. package/dist/source/components/typography.js +1 -1
  36. package/dist/source/components/ui/button.js +7 -7
  37. package/dist/source/components/ui/card.js +1 -1
  38. package/dist/source/components/ui/dialog.d.ts +4 -2
  39. package/dist/source/components/ui/dialog.js +10 -6
  40. package/dist/source/components/ui/index.d.ts +1 -0
  41. package/dist/source/components/ui/index.js +22 -0
  42. package/dist/source/components/ui/navigation.d.ts +3 -1
  43. package/dist/source/components/ui/navigation.js +17 -8
  44. package/dist/source/components/ui/sheet.d.ts +20 -0
  45. package/dist/source/components/ui/sheet.js +176 -0
  46. package/dist/source/components/ui/sonner.js +2 -2
  47. package/dist/source/lib/ui-release.d.ts +1 -1
  48. package/dist/source/lib/ui-release.js +1 -1
  49. package/docs/components/README.md +4 -1
  50. package/docs/components/application-layout.md +27 -28
  51. package/docs/components/avatar.md +1 -1
  52. package/docs/components/badge.md +5 -1
  53. package/docs/components/button.md +9 -1
  54. package/docs/components/card.md +5 -1
  55. package/docs/components/checkbox.md +1 -1
  56. package/docs/components/common.md +22 -1
  57. package/docs/components/dialog.md +14 -4
  58. package/docs/components/dropdown-menu.md +1 -1
  59. package/docs/components/input.md +1 -1
  60. package/docs/components/naeil-footer.md +4 -2
  61. package/docs/components/naeil-header.md +41 -12
  62. package/docs/components/naeil-locale.md +36 -0
  63. package/docs/components/navigation.md +10 -4
  64. package/docs/components/select.md +1 -1
  65. package/docs/components/sheet.md +50 -0
  66. package/docs/components/sonner.md +7 -1
  67. package/docs/components/switch.md +1 -1
  68. package/docs/components/tabs.md +1 -1
  69. package/docs/components/textarea.md +1 -1
  70. package/docs/design/public-ui-support.md +3 -1
  71. package/docs/design/v2-migration.md +13 -7
  72. package/docs/package-release.md +24 -8
  73. package/package.json +11 -10
  74. package/src/styles/components.css +40 -2
  75. package/src/styles/globals.css +6 -2
  76. package/src/styles/theme.css +6 -0
  77. package/licenses/Nunito-Sans-OFL.txt +0 -93
  78. package/licenses/React-MIT.txt +0 -21
  79. package/licenses/Storybook-MIT.txt +0 -21
  80. package/licenses/popper-MIT.txt +0 -25
  81. package/licenses/store2-MIT.txt +0 -22
  82. package/licenses/toggle-selection-MIT.txt +0 -21
@@ -1230,16 +1230,16 @@ import { cva as cva2 } from "class-variance-authority";
1230
1230
  import { Slot as Slot2 } from "radix-ui";
1231
1231
  import { jsx as jsx4 } from "react/jsx-runtime";
1232
1232
  var buttonVariants = cva2(
1233
- "inline-flex items-center justify-center gap-2 whitespace-nowrap ui-control ui-focus rounded-md text-base font-medium transition-colors duration-(--ui-motion-fast) cursor-pointer disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 ui-focus aria-invalid:border-error",
1233
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap ui-control ui-focus rounded-md text-base font-medium transition-colors duration-(--ui-motion-fast) cursor-pointer disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 ui-focus aria-invalid:border-error aria-pressed:inset-ring-2",
1234
1234
  {
1235
1235
  variants: {
1236
1236
  variant: {
1237
- default: "bg-primary text-primary-foreground hover:bg-primary-hover",
1238
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 ",
1239
- outline: "border border-border-strong bg-background hover:bg-surface-subtle",
1240
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1241
- ghost: "hover:bg-surface-subtle",
1242
- link: "text-primary underline-offset-4 hover:underline"
1237
+ default: "bg-primary text-primary-foreground hover:bg-primary-hover aria-pressed:bg-primary-hover aria-pressed:inset-ring-4 aria-pressed:inset-ring-primary-foreground aria-pressed:inset-shadow-[0_0_0_2px_var(--primary-hover)]",
1238
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 aria-pressed:inset-ring-4 aria-pressed:inset-ring-destructive-foreground aria-pressed:inset-shadow-[0_0_0_2px_var(--destructive)]",
1239
+ outline: "border border-border-strong bg-background hover:bg-surface-subtle aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground",
1240
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground",
1241
+ ghost: "hover:bg-surface-subtle aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground",
1242
+ link: "text-primary underline-offset-4 hover:underline aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground"
1243
1243
  },
1244
1244
  size: {
1245
1245
  default: "px-4 has-[>svg]:px-3",
@@ -1615,6 +1615,7 @@ function Navigation(_a) {
1615
1615
  toolbar,
1616
1616
  mobileToolbar,
1617
1617
  mobileFooter,
1618
+ omitEmptyNavigation = false,
1618
1619
  mobileOpen: controlledOpen,
1619
1620
  onMobileOpenChange,
1620
1621
  containerClassName,
@@ -1629,6 +1630,7 @@ function Navigation(_a) {
1629
1630
  "toolbar",
1630
1631
  "mobileToolbar",
1631
1632
  "mobileFooter",
1633
+ "omitEmptyNavigation",
1632
1634
  "mobileOpen",
1633
1635
  "onMobileOpenChange",
1634
1636
  "containerClassName",
@@ -1699,7 +1701,8 @@ function Navigation(_a) {
1699
1701
  setOpenGroup(null);
1700
1702
  }, [currentHref, setMobileOpen]);
1701
1703
  React.useEffect(() => {
1702
- const element = desktop.current;
1704
+ var _a2, _b2;
1705
+ const element = omitEmptyNavigation && !items.length ? (_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.parentElement) == null ? void 0 : _b2.querySelector(".ui-navigation-toolbar") : desktop.current;
1703
1706
  if (!element || typeof ResizeObserver === "undefined") return;
1704
1707
  const observer = new ResizeObserver(() => {
1705
1708
  if (element.getBoundingClientRect().width > 0) setMobileOpen(false);
@@ -1707,7 +1710,8 @@ function Navigation(_a) {
1707
1710
  });
1708
1711
  observer.observe(element);
1709
1712
  return () => observer.disconnect();
1710
- }, [setMobileOpen]);
1713
+ }, [setMobileOpen, omitEmptyNavigation, items.length]);
1714
+ const DesktopElement = omitEmptyNavigation && !items.length ? "div" : "nav";
1711
1715
  const active = (item) => {
1712
1716
  var _a2;
1713
1717
  return (_a2 = item.active) != null ? _a2 : !item.external && item.href === currentHref;
@@ -1756,10 +1760,12 @@ function Navigation(_a) {
1756
1760
  children: /* @__PURE__ */ jsx6("div", { className: cn("ui-navigation-frame", containerClassName), children: /* @__PURE__ */ jsxs4("div", { className: "ui-navigation-row", children: [
1757
1761
  /* @__PURE__ */ jsx6("div", { className: "ui-navigation-brand", children: brand }),
1758
1762
  /* @__PURE__ */ jsx6(
1759
- "nav",
1763
+ DesktopElement,
1760
1764
  {
1761
- ref: desktop,
1762
- "aria-label": labels.navigation,
1765
+ ref: (element) => {
1766
+ desktop.current = element;
1767
+ },
1768
+ "aria-label": omitEmptyNavigation && !items.length ? void 0 : labels.navigation,
1763
1769
  className: "ui-navigation-desktop",
1764
1770
  children: items.map(
1765
1771
  (item) => "items" in item ? /* @__PURE__ */ jsx6(
@@ -1811,7 +1817,7 @@ function Navigation(_a) {
1811
1817
  className: "ui-navigation-sheet ui-popup",
1812
1818
  "aria-describedby": void 0,
1813
1819
  onCloseAutoFocus: (event) => {
1814
- var _a2, _b2;
1820
+ var _a2, _b2, _c, _d;
1815
1821
  if (controlledOpen !== void 0 && (!mounted.current || state.current.open)) {
1816
1822
  event.preventDefault();
1817
1823
  return;
@@ -1825,7 +1831,10 @@ function Navigation(_a) {
1825
1831
  }
1826
1832
  if (trigger.current && trigger.current.getBoundingClientRect().width === 0) {
1827
1833
  event.preventDefault();
1828
- (_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a")) == null ? void 0 : _b2.focus();
1834
+ const target = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a");
1835
+ const toolbar2 = omitEmptyNavigation && !items.length ? (_c = (_b2 = desktop.current) == null ? void 0 : _b2.parentElement) == null ? void 0 : _c.querySelector(".ui-navigation-toolbar") : void 0;
1836
+ const toolbarTarget = !target && toolbar2 ? Array.from(toolbar2.querySelectorAll("button, a[href], input, select, textarea, [tabindex]")).find((element) => element.tabIndex >= 0 && !element.matches(":disabled") && element.getBoundingClientRect().width > 0 && element.getBoundingClientRect().height > 0) : void 0;
1837
+ (_d = target != null ? target : toolbarTarget) == null ? void 0 : _d.focus();
1829
1838
  }
1830
1839
  },
1831
1840
  children: [
@@ -1840,7 +1849,7 @@ function Navigation(_a) {
1840
1849
  }
1841
1850
  )
1842
1851
  ] }),
1843
- /* @__PURE__ */ jsx6(
1852
+ (!omitEmptyNavigation || items.length > 0) && /* @__PURE__ */ jsx6(
1844
1853
  "nav",
1845
1854
  {
1846
1855
  "aria-label": labels.navigation,
@@ -1886,7 +1895,7 @@ function Navigation(_a) {
1886
1895
  }
1887
1896
 
1888
1897
  // src/lib/ui-release.ts
1889
- var uiRelease = { version: "0.11.0", contractVersion: "1" };
1898
+ var uiRelease = { version: "0.13.0", contractVersion: "1" };
1890
1899
 
1891
1900
  // src/components/naeil-header-data.ts
1892
1901
  var headerLocales = ["en", "ko", "ja"];
@@ -2078,6 +2087,58 @@ function NaeilHeaderAccountControl({ locale, account, Link = NativeLink3 }) {
2078
2087
 
2079
2088
  // src/components/naeil-header-core.tsx
2080
2089
  import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2090
+ var naeilServices = Object.freeze(["abstorm", "home", "estimate", "jev"]);
2091
+ var serviceCompatibilityProps = [
2092
+ "brandSuffix",
2093
+ "statusSlot",
2094
+ "brandSlot",
2095
+ "toolbarSlot",
2096
+ "subNav",
2097
+ "brandLinkComponent",
2098
+ "projects",
2099
+ "projectsExternal",
2100
+ "showProjects",
2101
+ "showBlog",
2102
+ "translations",
2103
+ "className",
2104
+ "containerClassName",
2105
+ "style",
2106
+ "brandLinkProps",
2107
+ "mobileFooter",
2108
+ "omitEmptyNavigation"
2109
+ ];
2110
+ var warnedServiceProps = /* @__PURE__ */ new Set();
2111
+ function isDevelopment() {
2112
+ try {
2113
+ return process.env.NODE_ENV === "development";
2114
+ } catch (e) {
2115
+ return false;
2116
+ }
2117
+ }
2118
+ function warnServiceProp(name, message) {
2119
+ if (!isDevelopment() || warnedServiceProps.has(name)) return;
2120
+ warnedServiceProps.add(name);
2121
+ console.warn(message);
2122
+ }
2123
+ function isNaeilService(value) {
2124
+ return naeilServices.includes(value);
2125
+ }
2126
+ function NaeilHeader(input) {
2127
+ if (input.service === void 0) return /* @__PURE__ */ jsx9(HeaderComposition, __spreadValues({}, input));
2128
+ if (!isNaeilService(input.service)) {
2129
+ warnServiceProp("service", "NaeilHeader: invalid service; rendering the legacy profile.");
2130
+ const legacy = __spreadProps(__spreadValues({}, input), { service: void 0 });
2131
+ delete legacy.service;
2132
+ return /* @__PURE__ */ jsx9(HeaderComposition, __spreadValues({}, legacy));
2133
+ }
2134
+ const props = __spreadValues({}, input);
2135
+ for (const name of Object.keys(props)) {
2136
+ if (!serviceCompatibilityProps.includes(name) && name !== "data-slot" && name !== "data-global-header" && !name.startsWith("data-naeil-")) continue;
2137
+ delete props[name];
2138
+ warnServiceProp(name, `NaeilHeader: service profile ignores ${name}; use the shared service composition.`);
2139
+ }
2140
+ return /* @__PURE__ */ jsx9(HeaderComposition, __spreadProps(__spreadValues({}, props), { service: input.service }));
2141
+ }
2081
2142
  var subscribe = () => () => {
2082
2143
  };
2083
2144
  function NaeilHeaderThemeControl({ locale }) {
@@ -2114,8 +2175,9 @@ function NaeilHeaderLocaleControl({ locale, onLocaleChange }) {
2114
2175
  ] });
2115
2176
  }
2116
2177
  var NativeLink4 = (props) => /* @__PURE__ */ jsx9("a", __spreadValues({}, props));
2117
- function NaeilHeader(_a) {
2178
+ function HeaderComposition(_a) {
2118
2179
  var _b = _a, {
2180
+ service,
2119
2181
  locale,
2120
2182
  resolveHref = (path) => `https://naeil.dev/${locale}${path === "/" ? "" : path}`,
2121
2183
  brandHref,
@@ -2143,6 +2205,7 @@ function NaeilHeader(_a) {
2143
2205
  className,
2144
2206
  containerClassName
2145
2207
  } = _b, props = __objRest(_b, [
2208
+ "service",
2146
2209
  "locale",
2147
2210
  "resolveHref",
2148
2211
  "brandHref",
@@ -2171,6 +2234,11 @@ function NaeilHeader(_a) {
2171
2234
  "containerClassName"
2172
2235
  ]);
2173
2236
  var _b2, _c;
2237
+ if (service) {
2238
+ brandSuffix = service;
2239
+ showProjects = false;
2240
+ showBlog = false;
2241
+ }
2174
2242
  const content = useNaeilMessages(locale).header;
2175
2243
  const projectLinks = !showProjects ? [] : projects != null ? projects : headerProjects.map((project) => {
2176
2244
  var _a3, _b3, _c2;
@@ -2189,7 +2257,7 @@ function NaeilHeader(_a) {
2189
2257
  badge: project.status === "coming" ? /* @__PURE__ */ jsx9(Badge, { variant: "outline", children: (_c2 = translations == null ? void 0 : translations.project("status.coming")) != null ? _c2 : content.projects.status.coming }) : void 0
2190
2258
  };
2191
2259
  });
2192
- const brandDestination = brandHref != null ? brandHref : resolveHref("/");
2260
+ const brandDestination = brandHref != null ? brandHref : service ? "/" : resolveHref("/");
2193
2261
  const BrandLink = brandLinkComponent != null ? brandLinkComponent : Link;
2194
2262
  const _a2 = brandLinkProps != null ? brandLinkProps : {}, { onClick: onBrandClick } = _a2, brandProps = __objRest(_a2, ["onClick"]);
2195
2263
  const toolbar = /* @__PURE__ */ jsx9(
@@ -2215,11 +2283,11 @@ function NaeilHeader(_a) {
2215
2283
  __spreadProps(__spreadValues({
2216
2284
  href: brandDestination,
2217
2285
  "data-app-brand": true,
2218
- "aria-label": brandSlot == null ? ".naeil" : void 0
2286
+ "aria-label": service ? `.naeil ${service}` : brandSlot == null ? ".naeil" : void 0
2219
2287
  }, brandProps), {
2220
2288
  onClick: (event) => {
2221
2289
  onBrandClick == null ? void 0 : onBrandClick(event);
2222
- onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: ".naeil" });
2290
+ onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: service ? `.naeil ${service}` : ".naeil" });
2223
2291
  },
2224
2292
  children: brandSlot != null ? brandSlot : brandSuffix ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
2225
2293
  logo,
@@ -2229,7 +2297,7 @@ function NaeilHeader(_a) {
2229
2297
  );
2230
2298
  return /* @__PURE__ */ jsx9(
2231
2299
  Navigation,
2232
- __spreadProps(__spreadValues({}, props), {
2300
+ __spreadProps(__spreadValues(__spreadValues({}, props), service ? { "data-slot": "navigation", omitEmptyNavigation: true } : {}), {
2233
2301
  "data-global-header": true,
2234
2302
  "data-naeil-header": true,
2235
2303
  "data-naeil-chrome": "",
@@ -2381,5 +2449,6 @@ export {
2381
2449
  NaeilHeader,
2382
2450
  NaeilHeaderAccountControl,
2383
2451
  NaeilHeaderLocaleControl,
2384
- NaeilHeaderThemeControl
2452
+ NaeilHeaderThemeControl,
2453
+ naeilServices
2385
2454
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  assertLocale
3
- } from "./chunk-U65OQHZY.js";
3
+ } from "./chunk-WT6QXR7O.js";
4
4
  import {
5
5
  __spreadValues
6
6
  } from "./chunk-N3ETBM74.js";
@@ -6,6 +6,10 @@ import {
6
6
  // src/naeil-locale/routing.ts
7
7
  var naeilLocales = ["en", "ko", "ja"];
8
8
  var defaultLocale = "en";
9
+ function escapeRegExp(value) {
10
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11
+ }
12
+ var localePrefix = new RegExp(`^/(?:${naeilLocales.map(escapeRegExp).join("|")})(?:/|$)`);
9
13
  var naeilRouting = {
10
14
  locales: naeilLocales,
11
15
  defaultLocale,
@@ -28,7 +32,7 @@ var origins = {
28
32
  estimate: "https://abestimate.naeil.dev"
29
33
  };
30
34
  var protocolPrefixes = ["/api", "/auth", "/_next", "/_vercel", "/.well-known", "/health", "/invite", "/return", "/webhook", "/images", "/fonts"];
31
- var protocolFiles = ["/favicon.ico", "/icon.svg", "/apple-icon.png", "/robots.txt", "/sitemap.xml"];
35
+ var protocolFiles = ["/favicon.ico", "/favicon.svg", "/icon.svg", "/apple-icon.png", "/robots.txt", "/sitemap.xml"];
32
36
  function defineLocaleService(service) {
33
37
  var _a, _b, _c, _d;
34
38
  if (origins[service.id] !== service.origin) throw new Error("Service origin must match the exact owned origin");
@@ -36,7 +40,7 @@ function defineLocaleService(service) {
36
40
  if (!paths.length || new Set(paths).size !== paths.length) throw new Error("Route inventory is empty or duplicated");
37
41
  for (const path of paths) {
38
42
  if (!/^\/(?:[A-Za-z0-9._~-]+|\[[A-Za-z][A-Za-z0-9_]*\])(?:\/(?:[A-Za-z0-9._~-]+|\[[A-Za-z][A-Za-z0-9_]*\]))*$/.test(path) && path !== "/") throw new Error(`Invalid registered route: ${path}`);
39
- if (/^\/(?:en|ko|ja)(?:\/|$)/.test(path) || isProtocolPath(path, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(path)) || path.split("/").some((segment) => segment === "." || segment === "..")) throw new Error(`Locale/protocol path cannot be a UI route: ${path}`);
43
+ if (localePrefix.test(path) || isProtocolPath(path, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(path)) || path.split("/").some((segment) => segment === "." || segment === "..")) throw new Error(`Locale/protocol path cannot be a UI route: ${path}`);
40
44
  }
41
45
  for (const exception of (_b = service.uiProtocolExceptions) != null ? _b : []) if (exception.includes("[") || !paths.includes(exception)) throw new Error("Protocol UI exceptions must be exact registered routes");
42
46
  return Object.freeze(__spreadProps(__spreadValues({}, service), { routes: Object.freeze(service.routes.map((route) => typeof route === "string" ? route : Object.freeze(__spreadValues({}, route)))), protocolPrefixes: Object.freeze([...(_c = service.protocolPrefixes) != null ? _c : []]), uiProtocolExceptions: Object.freeze([...(_d = service.uiProtocolExceptions) != null ? _d : []]) }));
@@ -72,7 +76,7 @@ function invalid(reason, status = 404, locale) {
72
76
  return __spreadValues({ kind: "invalid", status, reason }, locale ? { locale } : {});
73
77
  }
74
78
  function splitInput(input, origin) {
75
- if (!input || /[\u0000-\u0020\u007f\\]/.test(input)) return;
79
+ if (!input || /[\u0000-\u0020\u007f]/.test(input)) return;
76
80
  let path = input;
77
81
  if (!path.startsWith("/")) {
78
82
  if (!path.startsWith(origin + "/") && path !== origin) return;
@@ -85,12 +89,14 @@ function splitInput(input, origin) {
85
89
  const queryAt = withoutHash.indexOf("?");
86
90
  return { pathname: queryAt < 0 ? withoutHash : withoutHash.slice(0, queryAt), search: queryAt < 0 ? "" : withoutHash.slice(queryAt), hash };
87
91
  }
88
- function safePath(path) {
92
+ function safePath(path, service) {
93
+ var _a;
89
94
  if (!path.startsWith("/") || path.includes("//")) return false;
95
+ const reserved = /* @__PURE__ */ new Set([...naeilLocales, ...[...protocolPrefixes, ...(_a = service.protocolPrefixes) != null ? _a : []].flatMap((prefix) => prefix.split("/").filter(Boolean))]);
90
96
  return path.split("/").every((segment) => {
91
97
  try {
92
98
  const decoded = decodeURIComponent(segment);
93
- return !/[\u0000-\u001f\u007f/\\%?#]/.test(decoded) && decoded !== "." && decoded !== ".." && (!/^(?:en|ko|ja|api|auth|_next|invite|return|webhook)$/.test(decoded) || decoded === segment);
99
+ return !/[\u0000-\u001f\u007f/\\%?#]/.test(decoded) && decoded !== "." && decoded !== ".." && (!reserved.has(decoded) || decoded === segment);
94
100
  } catch (e) {
95
101
  return false;
96
102
  }
@@ -113,7 +119,7 @@ function resolveLocaleUrl(inputService, input) {
113
119
  var _a, _b;
114
120
  const service = getLocaleService(inputService);
115
121
  const parts = splitInput(input, service.origin);
116
- if (!parts || !safePath(parts.pathname)) return invalid("Malformed or encoded path");
122
+ if (!parts || !safePath(parts.pathname, service)) return invalid("Malformed or encoded path");
117
123
  const rawPathname = parts.pathname;
118
124
  const pathname = rawPathname !== "/" && rawPathname.endsWith("/") ? rawPathname.slice(0, -1) : rawPathname;
119
125
  if (isProtocolPath(pathname, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(pathname))) return { kind: "protocol", pathname };
@@ -121,7 +127,7 @@ function resolveLocaleUrl(inputService, input) {
121
127
  const explicit = isNaeilLocale(segments[1]);
122
128
  const pathLocale = explicit ? segments[1] : void 0;
123
129
  const logicalPath = explicit ? "/" + segments.slice(2).join("/") : pathname;
124
- if (/^\/(?:en|ko|ja)(?:\/|$)/.test(logicalPath) || isProtocolPath(logicalPath, service) && !((_b = service.uiProtocolExceptions) == null ? void 0 : _b.includes(logicalPath))) return invalid("Nested locale or localized protocol path", 404, pathLocale);
130
+ if (localePrefix.test(logicalPath) || isProtocolPath(logicalPath, service) && !((_b = service.uiProtocolExceptions) == null ? void 0 : _b.includes(logicalPath))) return invalid("Nested locale or localized protocol path", 404, pathLocale);
125
131
  const route = matchRoute(service, logicalPath);
126
132
  if (!route) return invalid("Unregistered UI route", 404, pathLocale);
127
133
  const legacy = service.id !== "main";
@@ -137,7 +143,7 @@ function localePath(inputService, unlocalizedRoute, locale) {
137
143
  assertLocale(locale);
138
144
  const service = getLocaleService(inputService);
139
145
  const parts = splitInput(unlocalizedRoute, service.origin);
140
- if (!parts || /^\/(?:en|ko|ja)(?:\/|$)/.test(parts.pathname)) throw new Error("localePath requires an unlocalized route");
146
+ if (!parts || localePrefix.test(parts.pathname)) throw new Error("localePath requires an unlocalized route");
141
147
  const result = resolveLocaleUrl(service, parts.pathname);
142
148
  if (result.kind !== "ui") throw new Error("Unregistered UI destination");
143
149
  return prefixPath(result.logicalPath, locale) + (service.id === "main" ? parts.search : stripLegacyLang(parts.search)) + parts.hash;
@@ -13,6 +13,10 @@ import { NextIntlClientProvider } from "next-intl";
13
13
 
14
14
  // src/naeil-locale/routing.ts
15
15
  var naeilLocales = ["en", "ko", "ja"];
16
+ function escapeRegExp(value) {
17
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
+ }
19
+ var localePrefix = new RegExp(`^/(?:${naeilLocales.map(escapeRegExp).join("|")})(?:/|$)`);
16
20
  function isNaeilLocale(value) {
17
21
  return typeof value === "string" && naeilLocales.includes(value);
18
22
  }
@@ -27,7 +31,7 @@ var origins = {
27
31
  estimate: "https://abestimate.naeil.dev"
28
32
  };
29
33
  var protocolPrefixes = ["/api", "/auth", "/_next", "/_vercel", "/.well-known", "/health", "/invite", "/return", "/webhook", "/images", "/fonts"];
30
- var protocolFiles = ["/favicon.ico", "/icon.svg", "/apple-icon.png", "/robots.txt", "/sitemap.xml"];
34
+ var protocolFiles = ["/favicon.ico", "/favicon.svg", "/icon.svg", "/apple-icon.png", "/robots.txt", "/sitemap.xml"];
31
35
  function defineLocaleService(service) {
32
36
  var _a, _b, _c, _d;
33
37
  if (origins[service.id] !== service.origin) throw new Error("Service origin must match the exact owned origin");
@@ -35,7 +39,7 @@ function defineLocaleService(service) {
35
39
  if (!paths.length || new Set(paths).size !== paths.length) throw new Error("Route inventory is empty or duplicated");
36
40
  for (const path of paths) {
37
41
  if (!/^\/(?:[A-Za-z0-9._~-]+|\[[A-Za-z][A-Za-z0-9_]*\])(?:\/(?:[A-Za-z0-9._~-]+|\[[A-Za-z][A-Za-z0-9_]*\]))*$/.test(path) && path !== "/") throw new Error(`Invalid registered route: ${path}`);
38
- if (/^\/(?:en|ko|ja)(?:\/|$)/.test(path) || isProtocolPath(path, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(path)) || path.split("/").some((segment) => segment === "." || segment === "..")) throw new Error(`Locale/protocol path cannot be a UI route: ${path}`);
42
+ if (localePrefix.test(path) || isProtocolPath(path, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(path)) || path.split("/").some((segment) => segment === "." || segment === "..")) throw new Error(`Locale/protocol path cannot be a UI route: ${path}`);
39
43
  }
40
44
  for (const exception of (_b = service.uiProtocolExceptions) != null ? _b : []) if (exception.includes("[") || !paths.includes(exception)) throw new Error("Protocol UI exceptions must be exact registered routes");
41
45
  return Object.freeze(__spreadProps(__spreadValues({}, service), { routes: Object.freeze(service.routes.map((route) => typeof route === "string" ? route : Object.freeze(__spreadValues({}, route)))), protocolPrefixes: Object.freeze([...(_c = service.protocolPrefixes) != null ? _c : []]), uiProtocolExceptions: Object.freeze([...(_d = service.uiProtocolExceptions) != null ? _d : []]) }));
@@ -71,7 +75,7 @@ function invalid(reason, status = 404, locale) {
71
75
  return __spreadValues({ kind: "invalid", status, reason }, locale ? { locale } : {});
72
76
  }
73
77
  function splitInput(input, origin) {
74
- if (!input || /[\u0000-\u0020\u007f\\]/.test(input)) return;
78
+ if (!input || /[\u0000-\u0020\u007f]/.test(input)) return;
75
79
  let path = input;
76
80
  if (!path.startsWith("/")) {
77
81
  if (!path.startsWith(origin + "/") && path !== origin) return;
@@ -84,12 +88,14 @@ function splitInput(input, origin) {
84
88
  const queryAt = withoutHash.indexOf("?");
85
89
  return { pathname: queryAt < 0 ? withoutHash : withoutHash.slice(0, queryAt), search: queryAt < 0 ? "" : withoutHash.slice(queryAt), hash };
86
90
  }
87
- function safePath(path) {
91
+ function safePath(path, service) {
92
+ var _a;
88
93
  if (!path.startsWith("/") || path.includes("//")) return false;
94
+ const reserved = /* @__PURE__ */ new Set([...naeilLocales, ...[...protocolPrefixes, ...(_a = service.protocolPrefixes) != null ? _a : []].flatMap((prefix) => prefix.split("/").filter(Boolean))]);
89
95
  return path.split("/").every((segment) => {
90
96
  try {
91
97
  const decoded = decodeURIComponent(segment);
92
- return !/[\u0000-\u001f\u007f/\\%?#]/.test(decoded) && decoded !== "." && decoded !== ".." && (!/^(?:en|ko|ja|api|auth|_next|invite|return|webhook)$/.test(decoded) || decoded === segment);
98
+ return !/[\u0000-\u001f\u007f/\\%?#]/.test(decoded) && decoded !== "." && decoded !== ".." && (!reserved.has(decoded) || decoded === segment);
93
99
  } catch (e) {
94
100
  return false;
95
101
  }
@@ -112,7 +118,7 @@ function resolveLocaleUrl(inputService, input) {
112
118
  var _a, _b;
113
119
  const service = getLocaleService(inputService);
114
120
  const parts = splitInput(input, service.origin);
115
- if (!parts || !safePath(parts.pathname)) return invalid("Malformed or encoded path");
121
+ if (!parts || !safePath(parts.pathname, service)) return invalid("Malformed or encoded path");
116
122
  const rawPathname = parts.pathname;
117
123
  const pathname = rawPathname !== "/" && rawPathname.endsWith("/") ? rawPathname.slice(0, -1) : rawPathname;
118
124
  if (isProtocolPath(pathname, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(pathname))) return { kind: "protocol", pathname };
@@ -120,7 +126,7 @@ function resolveLocaleUrl(inputService, input) {
120
126
  const explicit = isNaeilLocale(segments[1]);
121
127
  const pathLocale = explicit ? segments[1] : void 0;
122
128
  const logicalPath = explicit ? "/" + segments.slice(2).join("/") : pathname;
123
- if (/^\/(?:en|ko|ja)(?:\/|$)/.test(logicalPath) || isProtocolPath(logicalPath, service) && !((_b = service.uiProtocolExceptions) == null ? void 0 : _b.includes(logicalPath))) return invalid("Nested locale or localized protocol path", 404, pathLocale);
129
+ if (localePrefix.test(logicalPath) || isProtocolPath(logicalPath, service) && !((_b = service.uiProtocolExceptions) == null ? void 0 : _b.includes(logicalPath))) return invalid("Nested locale or localized protocol path", 404, pathLocale);
124
130
  const route = matchRoute(service, logicalPath);
125
131
  if (!route) return invalid("Unregistered UI route", 404, pathLocale);
126
132
  const legacy = service.id !== "main";
@@ -136,7 +142,7 @@ function localePath(inputService, unlocalizedRoute, locale) {
136
142
  assertLocale(locale);
137
143
  const service = getLocaleService(inputService);
138
144
  const parts = splitInput(unlocalizedRoute, service.origin);
139
- if (!parts || /^\/(?:en|ko|ja)(?:\/|$)/.test(parts.pathname)) throw new Error("localePath requires an unlocalized route");
145
+ if (!parts || localePrefix.test(parts.pathname)) throw new Error("localePath requires an unlocalized route");
140
146
  const result = resolveLocaleUrl(service, parts.pathname);
141
147
  if (result.kind !== "ui") throw new Error("Unregistered UI destination");
142
148
  return prefixPath(result.logicalPath, locale) + (service.id === "main" ? parts.search : stripLegacyLang(parts.search)) + parts.hash;
@@ -236,6 +242,7 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
236
242
  const [intent, setIntent] = useState();
237
243
  const [error, setError] = useState();
238
244
  const lastRequest = useRef(void 0);
245
+ const timedOutIntent = useRef(void 0);
239
246
  const focus = useRef(void 0);
240
247
  const timer = useRef(void 0);
241
248
  const guards = useRef(/* @__PURE__ */ new Set());
@@ -245,6 +252,7 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
245
252
  }, []);
246
253
  const cancel = useCallback(() => {
247
254
  gate.cancel();
255
+ timedOutIntent.current = void 0;
248
256
  clearTimer();
249
257
  setIntent(void 0);
250
258
  setError(void 0);
@@ -253,6 +261,8 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
253
261
  const fail = useCallback((token, reason) => {
254
262
  var _a;
255
263
  if (((_a = gate.current()) == null ? void 0 : _a.token) !== token) return;
264
+ timedOutIntent.current = reason === "timeout" ? gate.current() : void 0;
265
+ focus.current = void 0;
256
266
  gate.cancel(token);
257
267
  clearTimer();
258
268
  setIntent(void 0);
@@ -262,12 +272,17 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
262
272
  var _a;
263
273
  const locale = assertLocale(language);
264
274
  if (gate.current()) return false;
275
+ lastRequest.current = { locale, options: options ? __spreadValues({}, options) : void 0 };
276
+ timedOutIntent.current = void 0;
265
277
  const current = resolveLocaleUrl(service, browserHref());
266
278
  if (current.kind !== "ui") {
267
279
  setError("unsupported-route");
268
280
  return false;
269
281
  }
270
- if (current.locale === locale && !(options == null ? void 0 : options.route) && !(options == null ? void 0 : options.href)) return false;
282
+ if (current.locale === locale && !(options == null ? void 0 : options.route) && !(options == null ? void 0 : options.href)) {
283
+ setError(void 0);
284
+ return false;
285
+ }
271
286
  const suffix = current.canonicalPath.slice(localePath(service, current.logicalPath, current.locale).length);
272
287
  let href;
273
288
  try {
@@ -283,7 +298,6 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
283
298
  }
284
299
  const request = gate.begin(href, locale);
285
300
  if (!request) return false;
286
- lastRequest.current = { locale, options };
287
301
  focus.current = captureFocus();
288
302
  setIntent(request);
289
303
  setError(void 0);
@@ -296,11 +310,26 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
296
310
  return false;
297
311
  }
298
312
  }, [service, gate, router, fail, transitionTimeoutMs]);
299
- const retry = useCallback(() => lastRequest.current ? changeLocale(lastRequest.current.locale, lastRequest.current.options) : false, [changeLocale]);
313
+ const retry = useCallback(() => {
314
+ const request = lastRequest.current;
315
+ if (!request || gate.current()) return false;
316
+ const current = resolveLocaleUrl(service, browserHref());
317
+ if (current.kind === "ui" && current.locale === request.locale) {
318
+ setError(void 0);
319
+ return false;
320
+ }
321
+ return changeLocale(request.locale, request.options);
322
+ }, [service, gate, changeLocale]);
300
323
  const arrive = useCallback((token, locale, committedPathname) => {
301
- if (token === void 0 || committedPathname !== window.location.pathname) return;
302
- const committed = gate.arrive(token, browserHref(), locale);
324
+ if (committedPathname !== window.location.pathname) return;
325
+ let committed = token === void 0 ? void 0 : gate.arrive(token, browserHref(), locale);
326
+ const late = timedOutIntent.current;
327
+ if (!committed && !gate.current() && late && (token === void 0 || token === late.token)) {
328
+ const current = resolveLocaleUrl(service, browserHref());
329
+ if (late.locale === locale && current.kind === "ui" && !current.alias && current.locale === locale && current.canonicalPath === late.href) committed = late;
330
+ }
303
331
  if (!committed) return;
332
+ timedOutIntent.current = void 0;
304
333
  clearTimer();
305
334
  setIntent(void 0);
306
335
  setError(void 0);
@@ -310,7 +339,7 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
310
339
  const savedFocus = focus.current;
311
340
  focus.current = void 0;
312
341
  requestAnimationFrame(() => restoreFocus(savedFocus));
313
- }, [gate, clearTimer, preferenceWritesEnabled]);
342
+ }, [service, gate, clearTimer, preferenceWritesEnabled]);
314
343
  const registerDepartureGuard = useCallback((guard) => {
315
344
  guards.current.add(guard);
316
345
  return () => {
@@ -341,7 +370,8 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
341
370
  };
342
371
  }, [cancel, gate, clearTimer]);
343
372
  useEffect(() => {
344
- const pending = gate.current();
373
+ var _a;
374
+ const pending = (_a = gate.current()) != null ? _a : timedOutIntent.current;
345
375
  if (!pending) return;
346
376
  const target = pending.href.split(/[?#]/, 1)[0];
347
377
  const current = resolveLocaleUrl(service, pathname != null ? pathname : "/");
@@ -11,7 +11,7 @@ import {
11
11
  naeilServices,
12
12
  resolveLocaleUrl,
13
13
  sameLogicalRoute
14
- } from "./chunk-U65OQHZY.js";
14
+ } from "./chunk-WT6QXR7O.js";
15
15
  import "./chunk-N3ETBM74.js";
16
16
 
17
17
  // src/naeil-locale/preference.ts
@@ -3,8 +3,8 @@ import {
3
3
  loadSharedMessages,
4
4
  mergeLocaleMessages,
5
5
  recoverMissingMessages
6
- } from "./chunk-4U64K4J3.js";
7
- import "./chunk-U65OQHZY.js";
6
+ } from "./chunk-24IL53QO.js";
7
+ import "./chunk-WT6QXR7O.js";
8
8
  import "./chunk-N3ETBM74.js";
9
9
  export {
10
10
  createLocaleMessageLoader,
@@ -2,7 +2,7 @@ import {
2
2
  createLocaleMessageLoader,
3
3
  loadSharedMessages,
4
4
  recoverMissingMessages
5
- } from "./chunk-4U64K4J3.js";
5
+ } from "./chunk-24IL53QO.js";
6
6
  import {
7
7
  assertLocale,
8
8
  getLocaleService,
@@ -11,7 +11,7 @@ import {
11
11
  naeilLocales,
12
12
  naeilRouting,
13
13
  resolveLocaleUrl
14
- } from "./chunk-U65OQHZY.js";
14
+ } from "./chunk-WT6QXR7O.js";
15
15
  import "./chunk-N3ETBM74.js";
16
16
 
17
17
  // src/naeil-locale/server.ts
@@ -62,10 +62,10 @@ function stripMarkers(headers) {
62
62
  return sanitized;
63
63
  }
64
64
  function sanitizeProtocolPassThrough(response, requestHeaders) {
65
- var _a;
66
65
  if (response.headers.get("x-middleware-next") !== "1" && !response.headers.has("x-middleware-rewrite")) return response;
67
- const forwarded = new Headers(requestHeaders);
68
- for (const key of ((_a = response.headers.get("x-middleware-override-headers")) != null ? _a : "").split(",")) {
66
+ const overrides = response.headers.get("x-middleware-override-headers");
67
+ const forwarded = new Headers(overrides === null ? requestHeaders : void 0);
68
+ for (const key of (overrides != null ? overrides : "").split(",").map((name) => name.trim())) {
69
69
  if (!key) continue;
70
70
  const value = response.headers.get(`x-middleware-request-${key}`);
71
71
  if (value !== null) forwarded.set(key, value);
@@ -79,7 +79,7 @@ function sanitizeProtocolPassThrough(response, requestHeaders) {
79
79
  names.push(key);
80
80
  response.headers.set(`x-middleware-request-${key}`, value);
81
81
  }
82
- response.headers.set("x-middleware-override-headers", names.join(","));
82
+ response.headers.set("x-middleware-override-headers", [...names, ...privateHeaders].join(","));
83
83
  return response;
84
84
  }
85
85
  function markPrivate(response) {
@@ -25,6 +25,8 @@ interface NavigationGroup {
25
25
  }
26
26
  type NavigationItem = NavigationLink | NavigationGroup;
27
27
  interface NavigationProps extends Omit<React.ComponentPropsWithoutRef<"header">, "children" | "onSelect"> {
28
+ /** Omit empty landmarks and dismiss toolbar-only sheets on desktop crossing. */
29
+ omitEmptyNavigation?: boolean;
28
30
  brand: React.ReactNode;
29
31
  items: NavigationItem[];
30
32
  /** Translated landmark/dialog/close labels are supplied by the application. */
@@ -45,6 +47,6 @@ interface NavigationProps extends Omit<React.ComponentPropsWithoutRef<"header">,
45
47
  /** Preserve modified clicks; applications may preventDefault for client routing/guards. */
46
48
  onNavigate?: (event: React.MouseEvent<HTMLAnchorElement>, item: NavigationLink) => void;
47
49
  }
48
- declare function Navigation({ brand, items, labels, Link, currentHref, toolbar, mobileToolbar, mobileFooter, mobileOpen: controlledOpen, onMobileOpenChange, containerClassName, onNavigate, className, ...props }: NavigationProps): react_jsx_runtime.JSX.Element;
50
+ declare function Navigation({ brand, items, labels, Link, currentHref, toolbar, mobileToolbar, mobileFooter, omitEmptyNavigation, mobileOpen: controlledOpen, onMobileOpenChange, containerClassName, onNavigate, className, ...props }: NavigationProps): react_jsx_runtime.JSX.Element;
49
51
 
50
52
  export { type NavigationLink as N, type NavigationProps as a, type NavigationLinkComponent as b, type NavigationItem as c, type NavigationLinkProps as d, Navigation as e, type NavigationGroup as f };
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { N as NavigationLink, b as NavigationLinkComponent, a as NavigationProps } from './navigation-D8N1tMTu.js';
3
+ import { N as NavigationLink, b as NavigationLinkComponent, a as NavigationProps } from './navigation-CXZapKac.js';
4
4
 
5
5
  interface SiteFooterProps extends React.ComponentPropsWithoutRef<"footer"> {
6
6
  brand?: React.ReactNode;