gbs-add-block 1.2.10 → 1.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # GBS Building Blocks 2.0 (v1.2.10)
1
+ # GBS Building Blocks 2.0 (v1.2.11)
2
2
 
3
3
  Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
4
4
 
@@ -13,9 +13,10 @@ DataGrid and Combobox use the redesigned core API and design system. Install the
13
13
  ```bash
14
14
  npx gbs-add-block -a DataGrid -beta
15
15
  npx gbs-add-block -a Combobox -beta
16
+ npx gbs-add-block -a DatePicker -beta
16
17
  ```
17
18
 
18
- ## What's New 🎉 (Ver 1.2.10)
19
+ ## What's New 🎉 (Ver 1.2.11)
19
20
 
20
21
  - Update Candidate for next major change 2.0.0
21
22
 
package/index.cjs CHANGED
@@ -32,13 +32,10 @@ const CONFIG = {
32
32
  "DataGrid",
33
33
  "BreadCrumb",
34
34
  "Bargraph",
35
- "UsePaginatedData",
36
- "UseUploader",
37
- ],
38
- betaComponents: [
39
- "DataGrid",
40
- "Combobox",
41
- ],
35
+ "UsePaginatedData",
36
+ "UseUploader",
37
+ ],
38
+ betaComponents: ["DataGrid", "Combobox", "DatePicker"],
42
39
  // Define component dependencies
43
40
  dependencies: {
44
41
  FormRenderer: ["Select", "MultiSelect", "Input", "DatePicker"],
@@ -46,19 +43,19 @@ const CONFIG = {
46
43
  docs: "https://gramprokit.vercel.app/",
47
44
  };
48
45
 
49
- const SOURCE_PATH = path.join(__dirname, "source", "components");
50
- const BETA_SOURCE_PATH = path.join(__dirname, "source", "beta-components");
51
- const DEFAULT_DEST_PATH = path.join(process.cwd(), "component-lib");
52
-
53
- const normalizeComponent = (component, availableComponents) =>
54
- availableComponents.find(
55
- (available) => available.toLowerCase() === component.toLowerCase(),
56
- );
57
-
58
- const getAvailableComponents = (beta = false) =>
59
- beta ? CONFIG.betaComponents : CONFIG.components;
60
-
61
- const getSourcePath = (beta = false) => (beta ? BETA_SOURCE_PATH : SOURCE_PATH);
46
+ const SOURCE_PATH = path.join(__dirname, "source", "components");
47
+ const BETA_SOURCE_PATH = path.join(__dirname, "source", "beta-components");
48
+ const DEFAULT_DEST_PATH = path.join(process.cwd(), "component-lib");
49
+
50
+ const normalizeComponent = (component, availableComponents) =>
51
+ availableComponents.find(
52
+ (available) => available.toLowerCase() === component.toLowerCase(),
53
+ );
54
+
55
+ const getAvailableComponents = (beta = false) =>
56
+ beta ? CONFIG.betaComponents : CONFIG.components;
57
+
58
+ const getSourcePath = (beta = false) => (beta ? BETA_SOURCE_PATH : SOURCE_PATH);
62
59
 
63
60
  const copyCommonFiles = async (destPath) => {
64
61
  const commonFiles = [
@@ -81,16 +78,19 @@ const checkComponentExists = (component, destPath) => {
81
78
  return fs.existsSync(componentPath);
82
79
  };
83
80
 
84
- const copyComponent = async (component, destPath, beta = false) => {
85
- try {
86
- const componentSrc = path.join(getSourcePath(beta), component.toLowerCase());
81
+ const copyComponent = async (component, destPath, beta = false) => {
82
+ try {
83
+ const componentSrc = path.join(
84
+ getSourcePath(beta),
85
+ component.toLowerCase(),
86
+ );
87
87
  const componentDest = path.join(destPath, component.toLowerCase());
88
88
 
89
- if (!fs.existsSync(componentSrc)) {
90
- throw new Error(
91
- `Component ${component} not found in ${beta ? "beta " : ""}source directory.`,
92
- );
93
- }
89
+ if (!fs.existsSync(componentSrc)) {
90
+ throw new Error(
91
+ `Component ${component} not found in ${beta ? "beta " : ""}source directory.`,
92
+ );
93
+ }
94
94
 
95
95
  await fs.copy(componentSrc, componentDest, { overwrite: true });
96
96
  console.log(
@@ -106,17 +106,21 @@ const copyComponent = async (component, destPath, beta = false) => {
106
106
  }
107
107
  };
108
108
 
109
- const installComponentWithDependencies = async (component, destPath, beta = false) => {
109
+ const installComponentWithDependencies = async (
110
+ component,
111
+ destPath,
112
+ beta = false,
113
+ ) => {
110
114
  // Get dependencies for the component
111
115
  const dependencies = CONFIG.dependencies[component] || [];
112
116
  const componentsToInstall = new Set([component, ...dependencies]);
113
117
 
114
118
  // Check which components need to be installed
115
- const pendingInstalls = beta
116
- ? Array.from(componentsToInstall)
117
- : Array.from(componentsToInstall).filter(
118
- (comp) => !checkComponentExists(comp, destPath),
119
- );
119
+ const pendingInstalls = beta
120
+ ? Array.from(componentsToInstall)
121
+ : Array.from(componentsToInstall).filter(
122
+ (comp) => !checkComponentExists(comp, destPath),
123
+ );
120
124
 
121
125
  if (pendingInstalls.length === 0) {
122
126
  console.log(
@@ -127,7 +131,7 @@ const installComponentWithDependencies = async (component, destPath, beta = fals
127
131
 
128
132
  // Install all pending components
129
133
  for (const comp of pendingInstalls) {
130
- await copyComponent(comp, destPath, beta);
134
+ await copyComponent(comp, destPath, beta);
131
135
  }
132
136
 
133
137
  if (dependencies.length > 0) {
@@ -140,7 +144,11 @@ const installComponentWithDependencies = async (component, destPath, beta = fals
140
144
  console.log(`\nFor documentation visit: ${CONFIG.docs}`);
141
145
  };
142
146
 
143
- const installMultipleComponents = async (components, destPath, beta = false) => {
147
+ const installMultipleComponents = async (
148
+ components,
149
+ destPath,
150
+ beta = false,
151
+ ) => {
144
152
  const allComponentsToInstall = new Set();
145
153
 
146
154
  // Collect all components and their dependencies
@@ -151,11 +159,11 @@ const installMultipleComponents = async (components, destPath, beta = false) =>
151
159
  });
152
160
 
153
161
  // Filter out already installed components
154
- const pendingInstalls = beta
155
- ? Array.from(allComponentsToInstall)
156
- : Array.from(allComponentsToInstall).filter(
157
- (comp) => !checkComponentExists(comp, destPath),
158
- );
162
+ const pendingInstalls = beta
163
+ ? Array.from(allComponentsToInstall)
164
+ : Array.from(allComponentsToInstall).filter(
165
+ (comp) => !checkComponentExists(comp, destPath),
166
+ );
159
167
 
160
168
  if (pendingInstalls.length === 0) {
161
169
  console.log(
@@ -168,7 +176,7 @@ const installMultipleComponents = async (components, destPath, beta = false) =>
168
176
 
169
177
  // Install all pending components
170
178
  for (const comp of pendingInstalls) {
171
- await copyComponent(comp, destPath, beta);
179
+ await copyComponent(comp, destPath, beta);
172
180
  }
173
181
 
174
182
  // Show dependency information
@@ -188,7 +196,7 @@ const installMultipleComponents = async (components, destPath, beta = false) =>
188
196
  console.log(`\nFor documentation visit: ${CONFIG.docs}`);
189
197
  };
190
198
 
191
- const interactiveComponentSelector = async (beta = false) => {
199
+ const interactiveComponentSelector = async (beta = false) => {
192
200
  return new Promise((resolve) => {
193
201
  const rl = readline.createInterface({
194
202
  input: process.stdin,
@@ -205,9 +213,9 @@ const interactiveComponentSelector = async (beta = false) => {
205
213
  "Use ↑/↓ arrow keys to navigate, SPACE to select/deselect, ENTER to install\n",
206
214
  );
207
215
 
208
- const availableComponents = getAvailableComponents(beta);
209
-
210
- availableComponents.forEach((component, index) => {
216
+ const availableComponents = getAvailableComponents(beta);
217
+
218
+ availableComponents.forEach((component, index) => {
211
219
  const isSelected = selectedComponents.has(component);
212
220
  const isCurrentIndex = index === currentIndex;
213
221
  const deps = CONFIG.dependencies[component]
@@ -237,13 +245,13 @@ const interactiveComponentSelector = async (beta = false) => {
237
245
  break;
238
246
  case "\u001b[B": // Down arrow
239
247
  currentIndex = Math.min(
240
- getAvailableComponents(beta).length - 1,
248
+ getAvailableComponents(beta).length - 1,
241
249
  currentIndex + 1,
242
250
  );
243
251
  renderMenu();
244
252
  break;
245
253
  case " ": // Space bar
246
- const component = getAvailableComponents(beta)[currentIndex];
254
+ const component = getAvailableComponents(beta)[currentIndex];
247
255
  if (selectedComponents.has(component)) {
248
256
  selectedComponents.delete(component);
249
257
  } else {
@@ -286,29 +294,29 @@ const parseMultipleComponents = (componentString) => {
286
294
  .filter((comp) => comp.length > 0);
287
295
  };
288
296
 
289
- const validateComponents = (components, beta = false) => {
290
- const availableComponents = getAvailableComponents(beta);
291
- const invalidComponents = components.filter(
292
- (comp) => !availableComponents.includes(comp),
293
- );
294
- if (invalidComponents.length > 0) {
295
- console.error(`Invalid components: ${invalidComponents.join(", ")}`);
296
- console.log(`\nAvailable ${beta ? "beta " : ""}components:`);
297
- availableComponents.forEach((comp) => console.log(`- ${comp}`));
298
- if (!beta) {
299
- console.log("\nRedesigned beta components (install with -beta):");
300
- CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
301
- }
302
- return false;
303
- }
304
- return true;
305
- };
306
-
307
- const main = async () => {
308
- const args = hideBin(process.argv).map((arg) =>
309
- arg === "-beta" ? "--beta" : arg,
310
- );
311
- const argv = yargs(args)
297
+ const validateComponents = (components, beta = false) => {
298
+ const availableComponents = getAvailableComponents(beta);
299
+ const invalidComponents = components.filter(
300
+ (comp) => !availableComponents.includes(comp),
301
+ );
302
+ if (invalidComponents.length > 0) {
303
+ console.error(`Invalid components: ${invalidComponents.join(", ")}`);
304
+ console.log(`\nAvailable ${beta ? "beta " : ""}components:`);
305
+ availableComponents.forEach((comp) => console.log(`- ${comp}`));
306
+ if (!beta) {
307
+ console.log("\nRedesigned beta components (install with -beta):");
308
+ CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
309
+ }
310
+ return false;
311
+ }
312
+ return true;
313
+ };
314
+
315
+ const main = async () => {
316
+ const args = hideBin(process.argv).map((arg) =>
317
+ arg === "-beta" ? "--beta" : arg,
318
+ );
319
+ const argv = yargs(args)
312
320
  .option("add", {
313
321
  alias: "a",
314
322
  describe:
@@ -320,44 +328,44 @@ const main = async () => {
320
328
  describe: "Interactive component selection mode",
321
329
  type: "boolean",
322
330
  })
323
- .option("list", {
324
- alias: "l",
325
- describe: "List available components",
326
- type: "boolean",
327
- })
328
- .option("beta", {
329
- describe: "Install redesigned beta components",
330
- type: "boolean",
331
- default: false,
332
- })
333
- .example("$0 -a Button", "Install a single component")
334
- .example("$0 -a Button,Card,Modal", "Install multiple components")
335
- .example("$0 -a DataGrid -beta", "Install the redesigned beta DataGrid")
336
- .example("$0 -a Combobox -beta", "Install the redesigned beta Combobox")
337
- .example("$0 -i", "Interactive selection mode")
331
+ .option("list", {
332
+ alias: "l",
333
+ describe: "List available components",
334
+ type: "boolean",
335
+ })
336
+ .option("beta", {
337
+ describe: "Install redesigned beta components",
338
+ type: "boolean",
339
+ default: false,
340
+ })
341
+ .example("$0 -a Button", "Install a single component")
342
+ .example("$0 -a Button,Card,Modal", "Install multiple components")
343
+ .example("$0 -a DataGrid -beta", "Install the redesigned beta DataGrid")
344
+ .example("$0 -a Combobox -beta", "Install the redesigned beta Combobox")
345
+ .example("$0 -i", "Interactive selection mode")
338
346
  .help().argv;
339
347
 
340
- // List components if requested
341
- if (argv.list) {
342
- const availableComponents = getAvailableComponents(argv.beta);
343
- console.log(`\nAvailable ${argv.beta ? "beta " : ""}components:`);
344
- availableComponents.forEach((comp) => {
345
- const deps = CONFIG.dependencies[comp]
346
- ? ` (requires: ${CONFIG.dependencies[comp].join(", ")})`
347
- : "";
348
- console.log(`- ${comp}${deps}`);
349
- });
350
- if (!argv.beta) {
351
- console.log("\nRedesigned beta components (install with -beta):");
352
- CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
353
- }
354
- return;
355
- }
348
+ // List components if requested
349
+ if (argv.list) {
350
+ const availableComponents = getAvailableComponents(argv.beta);
351
+ console.log(`\nAvailable ${argv.beta ? "beta " : ""}components:`);
352
+ availableComponents.forEach((comp) => {
353
+ const deps = CONFIG.dependencies[comp]
354
+ ? ` (requires: ${CONFIG.dependencies[comp].join(", ")})`
355
+ : "";
356
+ console.log(`- ${comp}${deps}`);
357
+ });
358
+ if (!argv.beta) {
359
+ console.log("\nRedesigned beta components (install with -beta):");
360
+ CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
361
+ }
362
+ return;
363
+ }
356
364
 
357
365
  // Interactive mode
358
- if (argv.interactive) {
359
- console.log("Starting interactive component selector...\n");
360
- const selectedComponents = await interactiveComponentSelector(argv.beta);
366
+ if (argv.interactive) {
367
+ console.log("Starting interactive component selector...\n");
368
+ const selectedComponents = await interactiveComponentSelector(argv.beta);
361
369
 
362
370
  if (selectedComponents.length === 0) {
363
371
  console.log("No components selected. Exiting...");
@@ -373,8 +381,8 @@ const main = async () => {
373
381
  await copyCommonFiles(destPath);
374
382
  }
375
383
 
376
- // Install selected components
377
- await installMultipleComponents(selectedComponents, destPath, argv.beta);
384
+ // Install selected components
385
+ await installMultipleComponents(selectedComponents, destPath, argv.beta);
378
386
  return;
379
387
  }
380
388
 
@@ -385,14 +393,16 @@ const main = async () => {
385
393
  process.exit(1);
386
394
  }
387
395
 
388
- // Parse components (single or multiple)
389
- const componentInput = argv.add;
390
- const components = parseMultipleComponents(componentInput).map((component) =>
391
- normalizeComponent(component, getAvailableComponents(argv.beta)) || component,
392
- );
393
-
394
- // Validate all components
395
- if (!validateComponents(components, argv.beta)) {
396
+ // Parse components (single or multiple)
397
+ const componentInput = argv.add;
398
+ const components = parseMultipleComponents(componentInput).map(
399
+ (component) =>
400
+ normalizeComponent(component, getAvailableComponents(argv.beta)) ||
401
+ component,
402
+ );
403
+
404
+ // Validate all components
405
+ if (!validateComponents(components, argv.beta)) {
396
406
  process.exit(1);
397
407
  }
398
408
 
@@ -406,14 +416,14 @@ const main = async () => {
406
416
  }
407
417
 
408
418
  // Install components
409
- if (components.length === 1) {
410
- // Single component installation (existing behavior)
411
- await installComponentWithDependencies(components[0], destPath, argv.beta);
412
- } else {
413
- // Multiple components installation
414
- await installMultipleComponents(components, destPath, argv.beta);
415
- }
416
- };
419
+ if (components.length === 1) {
420
+ // Single component installation (existing behavior)
421
+ await installComponentWithDependencies(components[0], destPath, argv.beta);
422
+ } else {
423
+ // Multiple components installation
424
+ await installMultipleComponents(components, destPath, argv.beta);
425
+ }
426
+ };
417
427
 
418
428
  main().catch((error) => {
419
429
  console.error("Error:", error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbs-add-block",
3
- "version": "1.2.10",
3
+ "version": "1.2.11",
4
4
  "description": "React Component Library",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,141 @@
1
+ # DatePicker and DateRangePicker
2
+
3
+ Two date fields built on one engine and styled to match the DataGrid and the
4
+ Combobox. No runtime dependencies besides React 19.
5
+
6
+ - **`<DatePicker>`** — one date.
7
+ - **`<DateRangePicker>`** — a start and an end, with a live preview between them.
8
+
9
+ Both support typed entry in the user's own date order, min/max limits, blocked
10
+ days, month and year panels, week numbers, forms, full keyboard control, dark
11
+ mode and right-to-left layouts.
12
+
13
+ ## Setup
14
+
15
+ ```ts
16
+ import { DatePicker, DateRangePicker } from "@/components/date-picker";
17
+ import "@/components/date-picker/styles.css";
18
+ ```
19
+
20
+ The stylesheet reuses the DataGrid's `--dg-*` variables when that stylesheet is
21
+ loaded, so every component shares a theme, and falls back to the same palette
22
+ when used on its own.
23
+
24
+ ## Basic usage
25
+
26
+ ```tsx
27
+ const [date, setDate] = useState<Date | null>(null);
28
+
29
+ <DatePicker label="Start date" value={date} onChange={setDate} />;
30
+
31
+ const [range, setRange] = useState<DateRange>({ start: null, end: null });
32
+
33
+ <DateRangePicker label="Period" value={range} onChange={setRange} />;
34
+ ```
35
+
36
+ Both are controlled with `value` + `onChange`, or uncontrolled with
37
+ `defaultValue`. `value`, `defaultValue`, `min` and `max` accept a `Date`, an ISO
38
+ `yyyy-mm-dd` string or a timestamp; `onChange` always gives you `Date` objects at
39
+ local midnight. The range picker's `onChange` also receives a `complete` flag,
40
+ which is `false` after the first of the two clicks.
41
+
42
+ ## Props
43
+
44
+ Shared by both components:
45
+
46
+ | Prop | Type | Default | Description |
47
+ | --- | --- | --- | --- |
48
+ | `min` / `max` | `Date \| string \| number` | — | Earliest and latest selectable day. |
49
+ | `isDateDisabled` | `(date: Date) => boolean` | — | Called per rendered day; return true to block it. |
50
+ | `locale` | `string` | runtime locale | Decides month names, field order and the first day of the week. |
51
+ | `weekStartsOn` | `0`–`6` | locale's first day | 0 is Sunday. |
52
+ | `format` | `Intl.DateTimeFormatOptions \| (date, locale) => string` | `{ year, month: "short", day }` | How the chosen date is written in the field. |
53
+ | `numberOfMonths` | `number` | `1` (range: `2`) | Months shown side by side. |
54
+ | `showWeekNumbers` | `boolean` | `false` | Adds an ISO week column. |
55
+ | `showToday` | `boolean` | `true` (range: `false`) | The "Today" shortcut in the footer. |
56
+ | `allowInput` | `boolean` | `true` | Let people type a date. When false the field is read-only and opens on click. |
57
+ | `fixedWeeks` | `boolean` | `true` | Always six week rows, so the popover never changes height. |
58
+ | `label`, `description`, `error` | `ReactNode` | — | Field label, hint and error message. `error` also marks the control invalid. |
59
+ | `placeholder` | `string` | the locale's pattern | e.g. `dd/mm/yyyy`. |
60
+ | `required`, `disabled`, `readOnly` | `boolean` | `false` | Field states. |
61
+ | `clearable` | `boolean` | `true` | Show the clear button. |
62
+ | `size` | `"sm"` \| `"md"` \| `"lg"` | `"md"` | Control height and font size. |
63
+ | `name` | `string` | — | Posts `yyyy-mm-dd` in a hidden input. The range picker posts `name-start` and `name-end`. |
64
+ | `className`, `classNames`, `style` | — | — | Styling hooks. Slots: `root`, `label`, `control`, `input`, `popover`, `calendar`, `day`, `footer`, `presets`. |
65
+ | `localeText` | `Partial<DatePickerLocaleText>` | English | Overrides UI text. |
66
+ | `onOpenChange` | `(open: boolean) => void` | — | Fires when the calendar opens or closes. |
67
+ | `ref` | `Ref<DatePickerHandle<T>>` | — | `open()`, `close()`, `toggle()`, `focus()`, `clear()`, `getValue()`. |
68
+
69
+ `DatePicker` adds `value` / `defaultValue` (`Date \| null`) and `closeOnSelect`
70
+ (default `true`). `DateRangePicker` adds `value` / `defaultValue`
71
+ (`{ start, end }`), `presets` and `closeOnSelect` (closes once both ends are set).
72
+
73
+ ## Typed entry
74
+
75
+ People can type instead of picking. Input is read in the locale's own field
76
+ order, so `03/04/2026` is 3 April in `en-GB` and 4 March in `en-US`. ISO
77
+ (`2026-03-12`) is accepted in every locale, month names work (`12 Mar 2026`), and
78
+ a missing year or month falls back to the month on screen. The calendar follows
79
+ along as you type. Text that isn't a date, or a date outside the limits, shows a
80
+ message when the field loses focus and leaves the value untouched.
81
+
82
+ ## Range presets
83
+
84
+ ```tsx
85
+ <DateRangePicker
86
+ value={range}
87
+ onChange={setRange}
88
+ presets={[
89
+ { label: "Last 7 days", range: { start: addDays(new Date(), -6), end: new Date() } },
90
+ { label: "This month", range: { start: startOfMonth(new Date()), end: new Date() } },
91
+ ]}
92
+ />
93
+ ```
94
+
95
+ ## Keyboard
96
+
97
+ | Keys | Action |
98
+ | --- | --- |
99
+ | **Enter**, **↓** | Open the calendar (from the field). |
100
+ | **←** / **→** | Previous / next day (swapped in right-to-left layouts). |
101
+ | **↑** / **↓** | Same weekday, previous / next week. |
102
+ | **Home** / **End** | First / last day of the week. |
103
+ | **Page Up** / **Page Down** | Previous / next month. |
104
+ | **Shift + Page Up/Down** | Previous / next year. |
105
+ | **Enter**, **Space** | Choose the focused day. |
106
+ | **Escape** | Close and return focus to the calendar button. |
107
+
108
+ Each month is a `role="grid"` where only the focused day is tabbable, which is
109
+ the WAI-ARIA pattern for a date picker. The calendar renders in the top layer
110
+ through the native Popover API, so it is never clipped by a scrolling parent
111
+ (including inside a DataGrid cell).
112
+
113
+ ## Theming
114
+
115
+ Override `--dp-*` variables (they default to the grid's `--dg-*`):
116
+
117
+ ```css
118
+ .dp-root { --dp-accent: #7c3aed; --dp-radius: 12px; --dp-day-size: 38px; }
119
+ ```
120
+
121
+ State attributes for styling: `data-state="open"`, `data-size`, `data-invalid`,
122
+ `data-disabled` on the root and control; `data-today`, `data-selected`,
123
+ `data-outside`, `data-weekend` on days; `data-in-range`, `data-range-start`,
124
+ `data-range-end` on day cells.
125
+
126
+ ## Headless use
127
+
128
+ `useDatePicker()` holds the whole engine (selection, visible months, typed
129
+ input, keyboard movement, popover state) and is exported if you want a different
130
+ UI on top. The framework-free helpers — `buildMonth`, `buildMonths`, `parseDate`,
131
+ `formatDate`, `moveByKey`, `normalizeRange`, `isInRange`, `toISODate` — are
132
+ exported from `@/components/date-picker/core` and also run on a server.
133
+
134
+ ## Known limits
135
+
136
+ - Dates only: no time-of-day or time zone selection. Values are local midnight.
137
+ - One range per field; multiple disjoint ranges aren't supported.
138
+ - Only the Gregorian calendar is handled, though month and weekday names,
139
+ field order and the first day of the week all follow the locale.
140
+ - When rendering on a server, pass `locale` explicitly so the server and the
141
+ browser format the field identically.