@jirawatpyk/aura-react 5.1.0 → 5.2.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 (48) hide show
  1. package/README.md +10 -0
  2. package/dist/aura.bundle.js +562 -183
  3. package/dist/cjs/index.cjs +562 -183
  4. package/dist/esm/AppShell.js +10 -2
  5. package/dist/esm/Avatar.js +4 -3
  6. package/dist/esm/BottomNav.js +2 -1
  7. package/dist/esm/Checkbox.js +3 -2
  8. package/dist/esm/Combobox.js +19 -19
  9. package/dist/esm/Command.js +1 -0
  10. package/dist/esm/DataTable.js +116 -29
  11. package/dist/esm/DatePicker.js +85 -22
  12. package/dist/esm/DropdownMenu.js +12 -0
  13. package/dist/esm/FileUpload.js +17 -1
  14. package/dist/esm/FormErrorSummary.js +19 -4
  15. package/dist/esm/Menu.js +20 -7
  16. package/dist/esm/NumberField.js +3 -1
  17. package/dist/esm/Pagination.js +3 -3
  18. package/dist/esm/Popover.js +26 -5
  19. package/dist/esm/RadioGroup.js +14 -1
  20. package/dist/esm/SideNav.js +2 -1
  21. package/dist/esm/Switch.js +1 -1
  22. package/dist/esm/Table.js +3 -2
  23. package/dist/esm/Tabs.js +11 -7
  24. package/dist/esm/TextField.js +2 -2
  25. package/dist/esm/Textarea.js +2 -2
  26. package/dist/esm/ThemeStyle.js +13 -3
  27. package/dist/esm/TimePicker.js +2 -1
  28. package/dist/esm/Toaster.js +1 -0
  29. package/dist/esm/Tooltip.js +54 -29
  30. package/dist/esm/colorScheme.js +7 -3
  31. package/dist/esm/colorSchemeScript.js +3 -3
  32. package/dist/esm/dates.js +34 -13
  33. package/dist/esm/internal.js +18 -7
  34. package/dist/esm/locale.js +6 -5
  35. package/dist/esm/strings.js +21 -0
  36. package/dist/esm/text.js +1 -0
  37. package/dist/esm/theme.js +3 -1
  38. package/dist/esm/useModal.js +31 -8
  39. package/dist/index.d.cts +1513 -0
  40. package/dist/index.d.ts +6 -1
  41. package/dist/server/index.cjs +51 -10
  42. package/dist/server/index.d.cts +182 -0
  43. package/dist/server/index.d.ts +6 -0
  44. package/dist/server/index.js +51 -10
  45. package/dist/styles.css +26 -5
  46. package/dist/styles.layer.css +26 -5
  47. package/package.json +19 -8
  48. package/scripts/aura-theme.mjs +31 -6
package/dist/index.d.ts CHANGED
@@ -1381,6 +1381,9 @@ export interface AuraStrings {
1381
1381
  loadingRows: string;
1382
1382
  range: (a: number | string, b: number | string, total: number) => string;
1383
1383
  page: (p: number, total: number) => string;
1384
+ /** Server paging without totalRows (5.2): the range and page when the total isn't known. */
1385
+ rangeOpen: (a: number | string, b: number | string) => string;
1386
+ pageOpen: (p: number) => string;
1384
1387
  prevPage: string;
1385
1388
  nextPage: string;
1386
1389
  rowCount: (c: number) => string;
@@ -1390,6 +1393,8 @@ export interface AuraStrings {
1390
1393
  timePlaceholder: string;
1391
1394
  timeInvalid: string;
1392
1395
  timeOutOfRange: (a: number | string, b: number | string) => string;
1396
+ /** A time inside the range that isTimeDisabled refuses (5.2). */
1397
+ timeUnavailable: string;
1393
1398
  dropFiles: string;
1394
1399
  browse: string;
1395
1400
  browseOne: string;
@@ -1454,7 +1459,7 @@ export interface ThemeStyleProps extends ThemeOptions {
1454
1459
  /** Scope the theme to this selector instead of the whole page (multi-tenant). */
1455
1460
  selector?: string | undefined;
1456
1461
  }
1457
- export declare function ThemeStyle(props: ThemeStyleProps): React$1.ReactElement;
1462
+ export declare function ThemeStyle(props: ThemeStyleProps): React$1.ReactElement | null;
1458
1463
  /** `system` follows the operating system and changes with it. */
1459
1464
  export type ColorScheme = "light" | "dark" | "system";
1460
1465
  export interface ColorSchemeOptions {
@@ -43,12 +43,21 @@ var pad = function(n2) {
43
43
  return (n2 < 10 ? "0" : "") + n2;
44
44
  };
45
45
  function toISO(d) {
46
- return d ? d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) : null;
46
+ if (!d) return null;
47
+ const y = String(d.getFullYear());
48
+ return ("000" + y).slice(-Math.max(4, y.length)) + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
49
+ }
50
+ function makeDate(y, monthIndex2, day) {
51
+ const d = new Date(2e3, 0, 1);
52
+ d.setFullYear(y, monthIndex2, day);
53
+ return d;
47
54
  }
48
55
  function fromISO(s) {
49
56
  if (!s) return null;
50
57
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
51
- return m ? new Date(+m[1], +m[2] - 1, +m[3]) : null;
58
+ if (!m) return null;
59
+ const d = makeDate(+m[1], +m[2] - 1, +m[3]);
60
+ return d.getMonth() === +m[2] - 1 && d.getDate() === +m[3] ? d : null;
52
61
  }
53
62
  var TAGS = { th: "th-TH", en: "en-GB", sv: "sv-SE" };
54
63
  function defaultCalendar(locale) {
@@ -94,7 +103,15 @@ function monthIndex(word) {
94
103
  return MONTHS[k] != null ? MONTHS[k] : -1;
95
104
  }
96
105
  function parseDate(text) {
97
- const s = String(text || "").trim();
106
+ const raw = String(text || "").trim();
107
+ const gregorian = /ค\.ศ\.|\d\s*(AD|CE)\b|^\s*(AD|CE)\s/i.test(raw);
108
+ let s = raw.replace(/พ\.ศ\.|ค\.ศ\./g, " ").replace(/(\d)\s*(BE|AD|CE)\b/gi, "$1 ").replace(/^\s*(BE|AD|CE)\s+/i, " ").replace(/\s+/g, " ").trim();
109
+ const eight = /^(\d{2})(\d{2})(\d{4})$/.exec(s);
110
+ if (eight) {
111
+ const a = +eight[3] >= 1e3 ? parseDate(eight[1] + "/" + eight[2] + "/" + eight[3] + (gregorian ? " \u0E04.\u0E28." : "")) : null;
112
+ if (a) return a;
113
+ s = s.slice(0, 4) + "-" + s.slice(4, 6) + "-" + s.slice(6);
114
+ }
98
115
  let m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(s), y, mo, d;
99
116
  if (m) {
100
117
  y = +m[1];
@@ -104,13 +121,13 @@ function parseDate(text) {
104
121
  d = +m[1];
105
122
  mo = +m[2];
106
123
  y = +m[3];
107
- } else if ((m = /^(\d{1,2})\s+(\S+)\s+(\d{4})$/.exec(s.replace(ERA, "").trim())) && monthIndex(m[2]) >= 0) {
124
+ } else if ((m = /^(\d{1,2})\s+(\S+)\s+(\d{4})$/.exec(s)) && monthIndex(m[2]) >= 0) {
108
125
  d = +m[1];
109
126
  mo = monthIndex(m[2]) + 1;
110
127
  y = +m[3];
111
128
  } else return null;
112
- if (y >= 2400) y -= 543;
113
- const dt = new Date(y, mo - 1, d);
129
+ if (y >= 2400 && !gregorian) y -= 543;
130
+ const dt = makeDate(y, mo - 1, d);
114
131
  return dt.getMonth() === mo - 1 && dt.getDate() === d ? toISO(dt) : null;
115
132
  }
116
133
  function todayIn(timeZone) {
@@ -149,6 +166,7 @@ function parseTime(text) {
149
166
  const m = /^(\d{1,2})(?:[:.](\d{2}))?$/.exec(s) || /^(\d{1,2})(\d{2})$/.exec(s);
150
167
  if (!m) return null;
151
168
  let hh = +m[1], mm = m[2] ? +m[2] : 0;
169
+ if ((am || pm) && (hh < 1 || hh > 12)) return null;
152
170
  if (pm && hh < 12) hh += 12;
153
171
  if (am && hh === 12) hh = 0;
154
172
  if (hh > 23 || mm > 59) return null;
@@ -244,6 +262,12 @@ var STRINGS = {
244
262
  page: function(p, total) {
245
263
  return "Page " + p + " of " + total;
246
264
  },
265
+ rangeOpen: function(a, b) {
266
+ return a + "\u2013" + b + " of many";
267
+ },
268
+ pageOpen: function(p) {
269
+ return "Page " + p;
270
+ },
247
271
  prevPage: "Previous page",
248
272
  nextPage: "Next page",
249
273
  totals: "Totals",
@@ -257,6 +281,7 @@ var STRINGS = {
257
281
  timeOutOfRange: function(a, b) {
258
282
  return "Choose a time between " + a + " and " + b;
259
283
  },
284
+ timeUnavailable: "That time isn't available. Choose another.",
260
285
  dropFiles: "Drag files here or",
261
286
  browse: "Choose files",
262
287
  browseOne: "Choose a file",
@@ -358,6 +383,12 @@ var STRINGS = {
358
383
  page: function(p, total) {
359
384
  return "\u0E2B\u0E19\u0E49\u0E32 " + p + " / " + total;
360
385
  },
386
+ rangeOpen: function(a, b) {
387
+ return a + "\u2013" + b + " \u0E08\u0E32\u0E01\u0E2B\u0E25\u0E32\u0E22\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23";
388
+ },
389
+ pageOpen: function(p) {
390
+ return "\u0E2B\u0E19\u0E49\u0E32 " + p;
391
+ },
361
392
  prevPage: "\u0E2B\u0E19\u0E49\u0E32\u0E01\u0E48\u0E2D\u0E19\u0E2B\u0E19\u0E49\u0E32",
362
393
  nextPage: "\u0E2B\u0E19\u0E49\u0E32\u0E16\u0E31\u0E14\u0E44\u0E1B",
363
394
  totals: "\u0E23\u0E27\u0E21",
@@ -371,6 +402,7 @@ var STRINGS = {
371
402
  timeOutOfRange: function(a, b) {
372
403
  return "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E40\u0E27\u0E25\u0E32\u0E23\u0E30\u0E2B\u0E27\u0E48\u0E32\u0E07 " + a + "\u2013" + b + " \u0E19.";
373
404
  },
405
+ timeUnavailable: "\u0E40\u0E27\u0E25\u0E32\u0E19\u0E35\u0E49\u0E44\u0E21\u0E48\u0E27\u0E48\u0E32\u0E07 \u0E40\u0E25\u0E37\u0E2D\u0E01\u0E40\u0E27\u0E25\u0E32\u0E2D\u0E37\u0E48\u0E19",
374
406
  dropFiles: "\u0E25\u0E32\u0E01\u0E44\u0E1F\u0E25\u0E4C\u0E21\u0E32\u0E27\u0E32\u0E07 \u0E2B\u0E23\u0E37\u0E2D",
375
407
  browse: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E44\u0E1F\u0E25\u0E4C",
376
408
  browseOne: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E44\u0E1F\u0E25\u0E4C",
@@ -472,6 +504,12 @@ var STRINGS = {
472
504
  page: function(p, total) {
473
505
  return "Sida " + p + " av " + total;
474
506
  },
507
+ rangeOpen: function(a, b) {
508
+ return a + "\u2013" + b + " av m\xE5nga";
509
+ },
510
+ pageOpen: function(p) {
511
+ return "Sida " + p;
512
+ },
475
513
  prevPage: "F\xF6reg\xE5ende sida",
476
514
  nextPage: "N\xE4sta sida",
477
515
  totals: "Summa",
@@ -485,6 +523,7 @@ var STRINGS = {
485
523
  timeOutOfRange: function(a, b) {
486
524
  return "V\xE4lj en tid mellan " + a + " och " + b;
487
525
  },
526
+ timeUnavailable: "Den tiden \xE4r inte ledig. V\xE4lj en annan.",
488
527
  dropFiles: "Dra filer hit eller",
489
528
  browse: "V\xE4lj filer",
490
529
  browseOne: "V\xE4lj en fil",
@@ -767,6 +806,8 @@ function createTheme(opts) {
767
806
  }
768
807
  if (s) check("light", "ink", "accent-lime (signal)", 4.5, INK, s[200]);
769
808
  function css(selector) {
809
+ if (selector !== void 0 && /[<{};@\r\n]|\/\*|\*\//.test(selector))
810
+ throw new Error('createTheme: "' + selector + '" is not a plain CSS selector');
770
811
  const light = selector ? selector : ':root, [data-theme="light"]';
771
812
  const dark = selector ? selector + ".dark, .dark " + selector + ", " + selector + '[data-theme="dark"], [data-theme="dark"] ' + selector : '.dark, [data-theme="dark"]';
772
813
  const system = selector ? '[data-theme="system"] ' + selector + ", " + selector + '[data-theme="system"]' : '[data-theme="system"]';
@@ -775,7 +816,7 @@ function createTheme(opts) {
775
816
  return " --aura-" + k + ": " + m[k] + ";";
776
817
  }).join("\n") + "\n}";
777
818
  }
778
- return "/* AURA theme" + (o.name ? ' "' + o.name + '"' : "") + ": brand " + o.brand + (o.signal ? ", signal " + o.signal : "") + (o.primary === "brand" ? ", brand primary buttons" : "") + ". Load after aura.css. Generated by createTheme. */\n" + block(light, L) + "\n" + block(dark, D) + "\n@media (prefers-color-scheme: dark) {\n" + block(system, D) + "\n}\n";
819
+ return "/* AURA theme" + (o.name ? ' "' + String(o.name).replace(/[*/<>\\]/g, "") + '"' : "") + ": brand " + o.brand + (o.signal ? ", signal " + o.signal : "") + (o.primary === "brand" ? ", brand primary buttons" : "") + ". Load after aura.css. Generated by createTheme. */\n" + block(light, L) + "\n" + block(dark, D) + "\n@media (prefers-color-scheme: dark) {\n" + block(system, D) + "\n}\n";
779
820
  }
780
821
  return {
781
822
  name: o.name || null,
@@ -794,9 +835,9 @@ function createTheme(opts) {
794
835
  // src/colorSchemeScript.ts
795
836
  var DEFAULT_KEY = "aura-color-scheme";
796
837
  function colorSchemeScript(options) {
797
- const key = JSON.stringify(options && options.storageKey || DEFAULT_KEY);
798
- const fallback = JSON.stringify(options && options.defaultScheme || "system");
799
- return "(function(){try{var s=localStorage.getItem(" + key + ");if(s!=='light'&&s!=='dark'&&s!=='system')s=" + fallback + `;var d=document.documentElement;d.setAttribute("data-theme",s);var dark=s==='dark'||(s==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches);d.classList.toggle("dark",dark);}catch(e){}})();`;
838
+ const key = JSON.stringify(options && options.storageKey || DEFAULT_KEY).replace(/</g, "\\u003c");
839
+ const fallback = JSON.stringify(options && options.defaultScheme || "system").replace(/</g, "\\u003c");
840
+ return "(function(){try{var s=localStorage.getItem(" + key + ");if(s!=='light'&&s!=='dark'&&s!=='system')s=" + fallback + `;var d=document.documentElement;d.setAttribute("data-theme",s);var m=window.matchMedia('(prefers-color-scheme: dark)');d.classList.toggle('dark',s==='dark'||(s==='system'&&m.matches));if(m.addEventListener)m.addEventListener('change',function(e){if(d.getAttribute('data-theme')==='system')d.classList.toggle('dark',e.matches)});}catch(e){}})();`;
800
841
  }
801
842
 
802
843
  // src/breakpoints.ts
@@ -0,0 +1,182 @@
1
+ import * as React$1 from 'react';
2
+
3
+ /** `warning` (5.1): needs attention but still works — Warning, Problem, Degraded, At risk. */
4
+ export type StatusTone = "neutral" | "progress" | "ready" | "warning" | "blocked";
5
+ /** ISO date string, `YYYY-MM-DD` (Gregorian — the era is display only). */
6
+ export type ISODate = string;
7
+ export interface DateDisplayOptions {
8
+ /** Components: the AuraProvider's locale, else `en`. `formatDate()` (no provider to read): `en` unless given (5.0; `th` before). */
9
+ locale?: "th" | "en" | "sv" | undefined;
10
+ /** `buddhist` (พ.ศ.; default for th) or `gregory` (ค.ศ.; default for en and sv). */
11
+ calendar?: "buddhist" | "gregory" | undefined;
12
+ }
13
+ export interface ThemeOptions {
14
+ /** The project's brand colour, #rgb or #rrggbb. Replaces AURA violet (links, focus ring, selection, info, progress, creative shadow in dark, mesh). */
15
+ brand: string;
16
+ /** Optional signal colour; replaces AURA lime in the accent dot and mesh only (success/ready stay green). */
17
+ signal?: string | undefined;
18
+ /** ink (default): primary buttons stay zinc ink. brand: primary buttons use the brand colour. */
19
+ primary?: "ink" | "brand" | undefined;
20
+ name?: string | undefined;
21
+ }
22
+ export interface ThemeCheck {
23
+ theme: "light" | "dark";
24
+ pair: string;
25
+ ratio: number;
26
+ target: number;
27
+ pass: boolean;
28
+ }
29
+ export interface Theme {
30
+ name: string | null;
31
+ brand: Record<50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900, string>;
32
+ signal: Record<50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900, string> | null;
33
+ light: Record<string, string>;
34
+ dark: Record<string, string>;
35
+ checks: ThemeCheck[];
36
+ ok: boolean;
37
+ /** CSS overriding the tokens. No selector: the whole page. A selector (".tenant-acme") scopes it. Load after aura.css. */
38
+ css(selector?: string): string;
39
+ }
40
+ /** Options for formatDate: locale/calendar plus a preset or any Intl.DateTimeFormat options. */
41
+ export type FormatDateOptions = DateDisplayOptions & {
42
+ format?: "short" | "long" | "numeric" | Intl.DateTimeFormatOptions | undefined;
43
+ };
44
+ export declare function toISO(d: Date | null | undefined): ISODate | null;
45
+ /** ISO → local Date; null for text that isn't a real date ("2026-02-31" no longer rolls over into March, 5.2). */
46
+ export declare function fromISO(s: ISODate | null | undefined): Date | null;
47
+ /** Format an ISO date for display: "18 Sept 2026" by default (English, Gregorian — 5.0); `{ locale: 'th' }` gives
48
+ * "18 ก.ย. 2569" (Buddhist era), `{ locale: 'sv' }` "18 sep. 2026". The same function from the package root and from
49
+ * `/server`. In components, `useFormatDate()` follows the AuraProvider's locale instead. */
50
+ export declare function formatDate(iso: ISODate | null | undefined, opts?: FormatDateOptions): string;
51
+ /** Parse typed text: dd/mm/yyyy (Buddhist years ≥ 2400 are converted), d-m-yyyy, d.m.yyyy, yyyy-mm-dd or '18 ก.ย. 2569' / '18 Sep 2026'. */
52
+ export declare function parseDate(text: string | null | undefined): ISODate | null;
53
+ /** Today as an ISO date in an IANA time zone (e.g. `Asia/Bangkok`), or in the runtime's own zone without one. (4.19) */
54
+ export declare function todayIn(timeZone?: string | null): ISODate;
55
+ /** 1536 → "1.5 KB". */
56
+ export declare function formatBytes(n: number | null | undefined): string;
57
+ /** Parse typed time: 9 · 09 · 930 · 0930 · 9:30 · 9.30 · 09.30 น. · 9:30 pm → "HH:mm" (24-hour) or null. */
58
+ export declare function parseTime(text: string | null | undefined): string | null;
59
+ /** The tone the pill would pick for a status word. */
60
+ declare function toneFor(status: unknown): StatusTone;
61
+ /** Every built-in label. Function entries build the text from their arguments. */
62
+ export interface AuraStrings {
63
+ close: string;
64
+ dismiss: string;
65
+ dismissToast: string;
66
+ showPassword: string;
67
+ filters: string;
68
+ search: string;
69
+ clearFilters: string;
70
+ results: (n: number) => string;
71
+ commandMenu: string;
72
+ commandPlaceholder: string;
73
+ commandHint: string;
74
+ errorSummary: (n: number) => string;
75
+ notifications: string;
76
+ mainNav: string;
77
+ breadcrumb: string;
78
+ navigation: string;
79
+ openNav: string;
80
+ collapseNav: string;
81
+ expandNav: string;
82
+ searching: string;
83
+ noMatches: string;
84
+ clear: (what?: string) => string;
85
+ keepTyping: (total: number) => string;
86
+ sortAsc: string;
87
+ sortDesc: string;
88
+ pin: string;
89
+ unpin: string;
90
+ moveLeft: string;
91
+ moveRight: string;
92
+ hideColumn: string;
93
+ resetColumns: string;
94
+ selectRows: string;
95
+ selectAllRows: string;
96
+ deselectAllRows: string;
97
+ selectAll: string;
98
+ selectRow: (k: React$1.ReactNode) => string;
99
+ selectedCount: (c: number) => string;
100
+ pinned: string;
101
+ columnOptions: (label?: string) => string;
102
+ column: (label?: string) => string;
103
+ columns: string;
104
+ showHideColumns: string;
105
+ empty: string;
106
+ loading: string;
107
+ loadingRows: string;
108
+ range: (a: number | string, b: number | string, total: number) => string;
109
+ page: (p: number, total: number) => string;
110
+ /** Server paging without totalRows (5.2): the range and page when the total isn't known. */
111
+ rangeOpen: (a: number | string, b: number | string) => string;
112
+ pageOpen: (p: number) => string;
113
+ prevPage: string;
114
+ nextPage: string;
115
+ rowCount: (c: number) => string;
116
+ totals: string;
117
+ actions: string;
118
+ optional: string;
119
+ timePlaceholder: string;
120
+ timeInvalid: string;
121
+ timeOutOfRange: (a: number | string, b: number | string) => string;
122
+ /** A time inside the range that isTimeDisabled refuses (5.2). */
123
+ timeUnavailable: string;
124
+ dropFiles: string;
125
+ browse: string;
126
+ browseOne: string;
127
+ remove: (n: number | string) => string;
128
+ fileTooBig: (max?: number | string) => string;
129
+ fileWrongType: string;
130
+ tooManyFiles: (n: number | string) => string;
131
+ colorScheme: string;
132
+ increase: string;
133
+ decrease: string;
134
+ stepDone: string;
135
+ stepOf: (i: number, total: number) => string;
136
+ schemeLight: string;
137
+ schemeDark: string;
138
+ schemeSystem: string;
139
+ uploading: string;
140
+ images: string;
141
+ pagination: string;
142
+ pageN: (p: number) => string;
143
+ accepts: (list?: string, max?: number | string) => string;
144
+ }
145
+ export declare const STRINGS: {
146
+ en: AuraStrings;
147
+ th: AuraStrings;
148
+ sv: AuraStrings;
149
+ };
150
+ /** WCAG contrast ratio of two #hex colours. */
151
+ export declare function contrast(a: string, b: string): number;
152
+ /** A 50–900 scale around one colour: same hue, AURA's lightness steps, chroma kept in gamut. */
153
+ declare function scale(hex: string): Theme["brand"];
154
+ /**
155
+ * createTheme({ brand, signal?, primary?: 'ink' | 'brand', name? })
156
+ * → { name, brand: {50…900}, signal: {50…900}|null, light: {token: #hex}, dark: {…}, checks: [{pair, ratio, target, pass}], ok, css(selector?) }
157
+ */
158
+ export declare function createTheme(opts: ThemeOptions): Theme;
159
+ /** `system` follows the operating system and changes with it. */
160
+ export type ColorScheme = "light" | "dark" | "system";
161
+ export interface ColorSchemeOptions {
162
+ /** localStorage key for the choice. Default `aura-color-scheme`. */
163
+ storageKey?: string | undefined;
164
+ /** Used when nothing is saved. Default `system`. */
165
+ defaultScheme?: ColorScheme | undefined;
166
+ }
167
+ /** The script ColorSchemeScript renders, as a string — for frameworks that want it in a raw <head> template. */
168
+ export declare function colorSchemeScript(options?: ColorSchemeOptions): string;
169
+ /** Min-width breakpoints in px, mirroring the aura-bp-* tokens. */
170
+ export declare const breakpoints: {
171
+ sm: 640;
172
+ md: 768;
173
+ lg: 1024;
174
+ xl: 1280;
175
+ };
176
+
177
+ export {
178
+ scale as brandScale,
179
+ toneFor as statusTone,
180
+ };
181
+
182
+ export {};
@@ -42,6 +42,7 @@ export type FormatDateOptions = DateDisplayOptions & {
42
42
  format?: "short" | "long" | "numeric" | Intl.DateTimeFormatOptions | undefined;
43
43
  };
44
44
  export declare function toISO(d: Date | null | undefined): ISODate | null;
45
+ /** ISO → local Date; null for text that isn't a real date ("2026-02-31" no longer rolls over into March, 5.2). */
45
46
  export declare function fromISO(s: ISODate | null | undefined): Date | null;
46
47
  /** Format an ISO date for display: "18 Sept 2026" by default (English, Gregorian — 5.0); `{ locale: 'th' }` gives
47
48
  * "18 ก.ย. 2569" (Buddhist era), `{ locale: 'sv' }` "18 sep. 2026". The same function from the package root and from
@@ -106,6 +107,9 @@ export interface AuraStrings {
106
107
  loadingRows: string;
107
108
  range: (a: number | string, b: number | string, total: number) => string;
108
109
  page: (p: number, total: number) => string;
110
+ /** Server paging without totalRows (5.2): the range and page when the total isn't known. */
111
+ rangeOpen: (a: number | string, b: number | string) => string;
112
+ pageOpen: (p: number) => string;
109
113
  prevPage: string;
110
114
  nextPage: string;
111
115
  rowCount: (c: number) => string;
@@ -115,6 +119,8 @@ export interface AuraStrings {
115
119
  timePlaceholder: string;
116
120
  timeInvalid: string;
117
121
  timeOutOfRange: (a: number | string, b: number | string) => string;
122
+ /** A time inside the range that isTimeDisabled refuses (5.2). */
123
+ timeUnavailable: string;
118
124
  dropFiles: string;
119
125
  browse: string;
120
126
  browseOne: string;
@@ -4,12 +4,21 @@ var pad = function(n2) {
4
4
  return (n2 < 10 ? "0" : "") + n2;
5
5
  };
6
6
  function toISO(d) {
7
- return d ? d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) : null;
7
+ if (!d) return null;
8
+ const y = String(d.getFullYear());
9
+ return ("000" + y).slice(-Math.max(4, y.length)) + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
10
+ }
11
+ function makeDate(y, monthIndex2, day) {
12
+ const d = new Date(2e3, 0, 1);
13
+ d.setFullYear(y, monthIndex2, day);
14
+ return d;
8
15
  }
9
16
  function fromISO(s) {
10
17
  if (!s) return null;
11
18
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
12
- return m ? new Date(+m[1], +m[2] - 1, +m[3]) : null;
19
+ if (!m) return null;
20
+ const d = makeDate(+m[1], +m[2] - 1, +m[3]);
21
+ return d.getMonth() === +m[2] - 1 && d.getDate() === +m[3] ? d : null;
13
22
  }
14
23
  var TAGS = { th: "th-TH", en: "en-GB", sv: "sv-SE" };
15
24
  function defaultCalendar(locale) {
@@ -55,7 +64,15 @@ function monthIndex(word) {
55
64
  return MONTHS[k] != null ? MONTHS[k] : -1;
56
65
  }
57
66
  function parseDate(text) {
58
- const s = String(text || "").trim();
67
+ const raw = String(text || "").trim();
68
+ const gregorian = /ค\.ศ\.|\d\s*(AD|CE)\b|^\s*(AD|CE)\s/i.test(raw);
69
+ let s = raw.replace(/พ\.ศ\.|ค\.ศ\./g, " ").replace(/(\d)\s*(BE|AD|CE)\b/gi, "$1 ").replace(/^\s*(BE|AD|CE)\s+/i, " ").replace(/\s+/g, " ").trim();
70
+ const eight = /^(\d{2})(\d{2})(\d{4})$/.exec(s);
71
+ if (eight) {
72
+ const a = +eight[3] >= 1e3 ? parseDate(eight[1] + "/" + eight[2] + "/" + eight[3] + (gregorian ? " \u0E04.\u0E28." : "")) : null;
73
+ if (a) return a;
74
+ s = s.slice(0, 4) + "-" + s.slice(4, 6) + "-" + s.slice(6);
75
+ }
59
76
  let m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(s), y, mo, d;
60
77
  if (m) {
61
78
  y = +m[1];
@@ -65,13 +82,13 @@ function parseDate(text) {
65
82
  d = +m[1];
66
83
  mo = +m[2];
67
84
  y = +m[3];
68
- } else if ((m = /^(\d{1,2})\s+(\S+)\s+(\d{4})$/.exec(s.replace(ERA, "").trim())) && monthIndex(m[2]) >= 0) {
85
+ } else if ((m = /^(\d{1,2})\s+(\S+)\s+(\d{4})$/.exec(s)) && monthIndex(m[2]) >= 0) {
69
86
  d = +m[1];
70
87
  mo = monthIndex(m[2]) + 1;
71
88
  y = +m[3];
72
89
  } else return null;
73
- if (y >= 2400) y -= 543;
74
- const dt = new Date(y, mo - 1, d);
90
+ if (y >= 2400 && !gregorian) y -= 543;
91
+ const dt = makeDate(y, mo - 1, d);
75
92
  return dt.getMonth() === mo - 1 && dt.getDate() === d ? toISO(dt) : null;
76
93
  }
77
94
  function todayIn(timeZone) {
@@ -110,6 +127,7 @@ function parseTime(text) {
110
127
  const m = /^(\d{1,2})(?:[:.](\d{2}))?$/.exec(s) || /^(\d{1,2})(\d{2})$/.exec(s);
111
128
  if (!m) return null;
112
129
  let hh = +m[1], mm = m[2] ? +m[2] : 0;
130
+ if ((am || pm) && (hh < 1 || hh > 12)) return null;
113
131
  if (pm && hh < 12) hh += 12;
114
132
  if (am && hh === 12) hh = 0;
115
133
  if (hh > 23 || mm > 59) return null;
@@ -205,6 +223,12 @@ var STRINGS = {
205
223
  page: function(p, total) {
206
224
  return "Page " + p + " of " + total;
207
225
  },
226
+ rangeOpen: function(a, b) {
227
+ return a + "\u2013" + b + " of many";
228
+ },
229
+ pageOpen: function(p) {
230
+ return "Page " + p;
231
+ },
208
232
  prevPage: "Previous page",
209
233
  nextPage: "Next page",
210
234
  totals: "Totals",
@@ -218,6 +242,7 @@ var STRINGS = {
218
242
  timeOutOfRange: function(a, b) {
219
243
  return "Choose a time between " + a + " and " + b;
220
244
  },
245
+ timeUnavailable: "That time isn't available. Choose another.",
221
246
  dropFiles: "Drag files here or",
222
247
  browse: "Choose files",
223
248
  browseOne: "Choose a file",
@@ -319,6 +344,12 @@ var STRINGS = {
319
344
  page: function(p, total) {
320
345
  return "\u0E2B\u0E19\u0E49\u0E32 " + p + " / " + total;
321
346
  },
347
+ rangeOpen: function(a, b) {
348
+ return a + "\u2013" + b + " \u0E08\u0E32\u0E01\u0E2B\u0E25\u0E32\u0E22\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23";
349
+ },
350
+ pageOpen: function(p) {
351
+ return "\u0E2B\u0E19\u0E49\u0E32 " + p;
352
+ },
322
353
  prevPage: "\u0E2B\u0E19\u0E49\u0E32\u0E01\u0E48\u0E2D\u0E19\u0E2B\u0E19\u0E49\u0E32",
323
354
  nextPage: "\u0E2B\u0E19\u0E49\u0E32\u0E16\u0E31\u0E14\u0E44\u0E1B",
324
355
  totals: "\u0E23\u0E27\u0E21",
@@ -332,6 +363,7 @@ var STRINGS = {
332
363
  timeOutOfRange: function(a, b) {
333
364
  return "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E40\u0E27\u0E25\u0E32\u0E23\u0E30\u0E2B\u0E27\u0E48\u0E32\u0E07 " + a + "\u2013" + b + " \u0E19.";
334
365
  },
366
+ timeUnavailable: "\u0E40\u0E27\u0E25\u0E32\u0E19\u0E35\u0E49\u0E44\u0E21\u0E48\u0E27\u0E48\u0E32\u0E07 \u0E40\u0E25\u0E37\u0E2D\u0E01\u0E40\u0E27\u0E25\u0E32\u0E2D\u0E37\u0E48\u0E19",
335
367
  dropFiles: "\u0E25\u0E32\u0E01\u0E44\u0E1F\u0E25\u0E4C\u0E21\u0E32\u0E27\u0E32\u0E07 \u0E2B\u0E23\u0E37\u0E2D",
336
368
  browse: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E44\u0E1F\u0E25\u0E4C",
337
369
  browseOne: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E44\u0E1F\u0E25\u0E4C",
@@ -433,6 +465,12 @@ var STRINGS = {
433
465
  page: function(p, total) {
434
466
  return "Sida " + p + " av " + total;
435
467
  },
468
+ rangeOpen: function(a, b) {
469
+ return a + "\u2013" + b + " av m\xE5nga";
470
+ },
471
+ pageOpen: function(p) {
472
+ return "Sida " + p;
473
+ },
436
474
  prevPage: "F\xF6reg\xE5ende sida",
437
475
  nextPage: "N\xE4sta sida",
438
476
  totals: "Summa",
@@ -446,6 +484,7 @@ var STRINGS = {
446
484
  timeOutOfRange: function(a, b) {
447
485
  return "V\xE4lj en tid mellan " + a + " och " + b;
448
486
  },
487
+ timeUnavailable: "Den tiden \xE4r inte ledig. V\xE4lj en annan.",
449
488
  dropFiles: "Dra filer hit eller",
450
489
  browse: "V\xE4lj filer",
451
490
  browseOne: "V\xE4lj en fil",
@@ -728,6 +767,8 @@ function createTheme(opts) {
728
767
  }
729
768
  if (s) check("light", "ink", "accent-lime (signal)", 4.5, INK, s[200]);
730
769
  function css(selector) {
770
+ if (selector !== void 0 && /[<{};@\r\n]|\/\*|\*\//.test(selector))
771
+ throw new Error('createTheme: "' + selector + '" is not a plain CSS selector');
731
772
  const light = selector ? selector : ':root, [data-theme="light"]';
732
773
  const dark = selector ? selector + ".dark, .dark " + selector + ", " + selector + '[data-theme="dark"], [data-theme="dark"] ' + selector : '.dark, [data-theme="dark"]';
733
774
  const system = selector ? '[data-theme="system"] ' + selector + ", " + selector + '[data-theme="system"]' : '[data-theme="system"]';
@@ -736,7 +777,7 @@ function createTheme(opts) {
736
777
  return " --aura-" + k + ": " + m[k] + ";";
737
778
  }).join("\n") + "\n}";
738
779
  }
739
- return "/* AURA theme" + (o.name ? ' "' + o.name + '"' : "") + ": brand " + o.brand + (o.signal ? ", signal " + o.signal : "") + (o.primary === "brand" ? ", brand primary buttons" : "") + ". Load after aura.css. Generated by createTheme. */\n" + block(light, L) + "\n" + block(dark, D) + "\n@media (prefers-color-scheme: dark) {\n" + block(system, D) + "\n}\n";
780
+ return "/* AURA theme" + (o.name ? ' "' + String(o.name).replace(/[*/<>\\]/g, "") + '"' : "") + ": brand " + o.brand + (o.signal ? ", signal " + o.signal : "") + (o.primary === "brand" ? ", brand primary buttons" : "") + ". Load after aura.css. Generated by createTheme. */\n" + block(light, L) + "\n" + block(dark, D) + "\n@media (prefers-color-scheme: dark) {\n" + block(system, D) + "\n}\n";
740
781
  }
741
782
  return {
742
783
  name: o.name || null,
@@ -755,9 +796,9 @@ function createTheme(opts) {
755
796
  // src/colorSchemeScript.ts
756
797
  var DEFAULT_KEY = "aura-color-scheme";
757
798
  function colorSchemeScript(options) {
758
- const key = JSON.stringify(options && options.storageKey || DEFAULT_KEY);
759
- const fallback = JSON.stringify(options && options.defaultScheme || "system");
760
- return "(function(){try{var s=localStorage.getItem(" + key + ");if(s!=='light'&&s!=='dark'&&s!=='system')s=" + fallback + `;var d=document.documentElement;d.setAttribute("data-theme",s);var dark=s==='dark'||(s==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches);d.classList.toggle("dark",dark);}catch(e){}})();`;
799
+ const key = JSON.stringify(options && options.storageKey || DEFAULT_KEY).replace(/</g, "\\u003c");
800
+ const fallback = JSON.stringify(options && options.defaultScheme || "system").replace(/</g, "\\u003c");
801
+ return "(function(){try{var s=localStorage.getItem(" + key + ");if(s!=='light'&&s!=='dark'&&s!=='system')s=" + fallback + `;var d=document.documentElement;d.setAttribute("data-theme",s);var m=window.matchMedia('(prefers-color-scheme: dark)');d.classList.toggle('dark',s==='dark'||(s==='system'&&m.matches));if(m.addEventListener)m.addEventListener('change',function(e){if(d.getAttribute('data-theme')==='system')d.classList.toggle('dark',e.matches)});}catch(e){}})();`;
761
802
  }
762
803
 
763
804
  // src/breakpoints.ts
package/dist/styles.css CHANGED
@@ -212,7 +212,7 @@ a.aura-btn { text-decoration: none; }
212
212
 
213
213
  /* Menu — popover list for column options and the column picker */
214
214
  .aura-menu {
215
- position: fixed; z-index: var(--aura-z-menu); min-width: 200px; padding: var(--aura-space-1);
215
+ position: fixed; z-index: var(--aura-z-menu); min-width: 200px; padding: var(--aura-space-1); overflow-y: auto; overscroll-behavior: contain;
216
216
  background: var(--aura-bg-surface); color: var(--aura-fg-primary);
217
217
  border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-md);
218
218
  box-shadow: var(--aura-shadow-enterprise);
@@ -520,6 +520,8 @@ a.aura-icon-btn { text-decoration: none; }
520
520
  font-family: var(--font-sans); font-size: 12px; line-height: 1.5; pointer-events: none;
521
521
  animation: aura-fade-in var(--aura-duration-fast) var(--aura-ease);
522
522
  }
523
+ /* The Tooltip component's own tips can be hovered (WCAG 1.4.13, 5.1.1); rail labels and cell tips stay click-through. */
524
+ .aura-tooltip--hoverable { pointer-events: auto; }
523
525
  @keyframes aura-fade-in { from { opacity: 0; } }
524
526
 
525
527
  /* ---------- Dialog ---------- */
@@ -677,6 +679,8 @@ a.aura-tab { text-decoration: none; }
677
679
  .aura-avatar__status {
678
680
  position: absolute; right: -1px; bottom: -1px; width: 30%; height: 30%; min-width: 8px; min-height: 8px;
679
681
  border-radius: var(--aura-radius-full); background: var(--aura-accent-dot); box-shadow: 0 0 0 2px var(--aura-bg-surface);
682
+ /* 5.2: the lime fill is 1.2:1 on the white ring; the edge carries the 3:1 (WCAG 1.4.11). */
683
+ border: 1.5px solid var(--aura-fg-positive); box-sizing: border-box;
680
684
  }
681
685
 
682
686
  /* ---------- Combobox ---------- */
@@ -746,7 +750,7 @@ a.aura-tab { text-decoration: none; }
746
750
  .aura-cal__day.is-outside { color: var(--aura-fg-tertiary); }
747
751
  .aura-cal__day.is-today { box-shadow: inset 0 0 0 1px var(--aura-border-control); font-weight: 600; }
748
752
  .aura-cal__day.is-selected { background: var(--aura-button-primary-bg); color: var(--aura-button-primary-fg); box-shadow: none; }
749
- .aura-cal__day:disabled { opacity: var(--aura-disabled-opacity); cursor: not-allowed; background: transparent; text-decoration: line-through; }
753
+ .aura-cal__day:disabled, .aura-cal__day[aria-disabled='true'] { opacity: var(--aura-disabled-opacity); cursor: not-allowed; background: transparent; text-decoration: line-through; }
750
754
  .aura-cal__day:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 1px; }
751
755
  .aura-cal__grid td.is-in-range { background: var(--aura-bg-selected); }
752
756
  .aura-cal__grid td.is-range-start { background: linear-gradient(to right, transparent 50%, var(--aura-bg-selected) 50%); }
@@ -919,6 +923,8 @@ button.aura-stat { font: inherit; width: 100%; cursor: pointer; }
919
923
  .aura-upload__thumb.is-icon { display: inline-flex; align-items: center; justify-content: center; color: var(--aura-fg-secondary); }
920
924
  .aura-upload__meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
921
925
  .aura-upload__name { font-size: 13px; font-weight: 500; color: var(--aura-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
926
+ a.aura-upload__name { color: var(--aura-fg-accent); text-decoration: underline; text-underline-offset: 2px; border-radius: var(--aura-radius-xs); }
927
+ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 2px; }
922
928
  .aura-upload__sub { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--aura-fg-secondary); }
923
929
  .aura-upload__item.is-error .aura-upload__sub { color: var(--aura-fg-danger); }
924
930
  .aura-upload__bar { display: block; height: 4px; border-radius: var(--aura-radius-full); background: var(--aura-progress-track); box-shadow: inset 0 0 0 1px var(--aura-progress-track-edge); overflow: hidden; }
@@ -1040,8 +1046,9 @@ button.aura-stat { font: inherit; width: 100%; cursor: pointer; }
1040
1046
  position: fixed; z-index: var(--aura-z-menu); box-sizing: border-box; width: max-content; min-width: 200px; max-width: min(360px, calc(100vw - 16px));
1041
1047
  background: var(--aura-bg-surface); color: var(--aura-fg-primary); border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg);
1042
1048
  box-shadow: var(--aura-shadow-overlay); font-family: var(--font-sans); font-size: 14px; outline: none; animation: aura-fade-in var(--aura-duration-fast) var(--aura-ease);
1049
+ overflow-y: auto; overscroll-behavior: contain;
1043
1050
  }
1044
- .aura-popover__head { display: flex; align-items: center; justify-content: space-between; gap: var(--aura-space-2); padding: var(--aura-space-2) var(--aura-space-2) 0 var(--aura-space-4); }
1051
+ .aura-popover__head { display: flex; align-items: center; justify-content: space-between; gap: var(--aura-space-2); padding: var(--aura-space-2) var(--aura-space-2) 0 var(--aura-space-4); position: sticky; top: 0; z-index: 1; background: var(--aura-bg-surface); } /* 5.2: stays while a tall popover scrolls */
1045
1052
  .aura-popover__title { margin: 0; font-size: 14px; font-weight: 600; }
1046
1053
  .aura-popover__body { padding: var(--aura-space-4); }
1047
1054
  .aura-popover__head + .aura-popover__body { padding-top: var(--aura-space-2); }
@@ -1141,7 +1148,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1141
1148
  .aura-segmented.is-full .aura-segmented__option { flex: 1 1 0; min-width: 0; }
1142
1149
  .aura-segmented__option.is-icon { padding: 0; width: 36px; }
1143
1150
  .aura-segmented__option:hover:not(:disabled):not(.is-selected) { color: var(--aura-fg-primary); }
1144
- .aura-segmented__option.is-selected { background: var(--aura-bg-surface); color: var(--aura-fg-primary); box-shadow: var(--aura-shadow-enterprise); }
1151
+ .aura-segmented__option.is-selected { background: var(--aura-bg-surface); color: var(--aura-fg-primary); box-shadow: inset 0 0 0 1px var(--aura-border-control), var(--aura-shadow-enterprise); } /* 5.2: the edge marks the choice at 3:1; the fill alone was 1.04:1 on the track */
1145
1152
  .aura-segmented__option:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 1px; }
1146
1153
  .aura-segmented__option:disabled { opacity: var(--aura-disabled-opacity); cursor: default; }
1147
1154
  .aura-segmented--sm .aura-segmented__option { height: 24px; padding: 0 var(--aura-space-3); font-size: 12px; }
@@ -1423,7 +1430,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1423
1430
  /* ---------- 4.20: DataTable width queries and cards, one markup ---------- */
1424
1431
  /* Each .aura-table-q is the table's width × 10000 / its threshold (DataTable.tsx, Q), so `(width < 9999.5px)` is true
1425
1432
  * exactly when the table is narrower than stackBelow or a hideBelow width — before hydration, at any width. */
1426
- .aura-table-box { overflow-x: clip; }
1433
+ .aura-table-box { overflow-x: clip; width: 100%; } /* 5.1.1: container-type boxes have no content width; without this a table in a centred flex column collapsed to 0 */
1427
1434
  .aura-table-q { width: calc(100% * var(--aura-q-scale)); container-type: inline-size; }
1428
1435
  .aura-table-q--stack { container-name: aura-stack; }
1429
1436
  .aura-table-q--h1 { container-name: aura-h1; }
@@ -1485,3 +1492,17 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1485
1492
  .aura-table__foot { border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); margin-top: var(--aura-space-2); background: var(--aura-bg-surface); }
1486
1493
  .aura-table__empty { background: var(--aura-bg-surface); border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); }
1487
1494
  }
1495
+
1496
+ /* ---------- 5.1.1: Windows High Contrast / forced colours ---------- */
1497
+ /* Backgrounds are replaced in forced colours, so controls that show state only by fill get borders and system colours. */
1498
+ @media (forced-colors: active) {
1499
+ .aura-switch { border: 1px solid CanvasText; }
1500
+ .aura-switch__thumb { background: CanvasText; forced-color-adjust: none; }
1501
+ .aura-switch.is-on { background: Highlight; forced-color-adjust: none; }
1502
+ .aura-switch.is-on .aura-switch__thumb { background: HighlightText; }
1503
+ .aura-progress__track, .aura-upload__bar { border: 1px solid CanvasText; }
1504
+ .aura-progress__bar, .aura-upload__bar > span { background: Highlight; forced-color-adjust: none; }
1505
+ .aura-segmented__option.is-selected { outline: 2px solid Highlight; outline-offset: -2px; }
1506
+ .aura-tab { border-bottom-color: Canvas; }
1507
+ .aura-tab.is-active { border-bottom-color: Highlight; }
1508
+ }