najm-kit 2.8.0 → 2.8.2

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/CHANGELOG.md CHANGED
@@ -1,4 +1,23 @@
1
- # Changelog
1
+ # Changelog
2
+
3
+ ## 2.8.2 - 2026-08-08
4
+
5
+ - Added the framework-neutral `najm-kit/person-images` subpath. It ships a
6
+ built-in resolver for `child`, `adult`, `parent`, and `family` roles with
7
+ the seven WebP illustrations embedded as base64 data URLs, plus
8
+ `createPersonImageResolver` so an application can declare its own role
9
+ names (`teacher`, `student`, `doctor`, `driver`, …) and TypeScript catches
10
+ unknown role strings at the call site. The root `najm-kit` entry stays
11
+ unchanged and does not pull in the person images.
12
+
13
+ ## 2.8.1 - 2026-08-08
14
+
15
+ - Added schema-driven form development tools to `NajmAppProvider`. Passing
16
+ `formDevTools` enables F8 filling for every `NForm` and `WizardForm` without
17
+ an application helper or additional provider.
18
+ - Added built-in Zod 4 form-value generation with per-form overrides for
19
+ relation fields and other application-owned values. Existing explicit
20
+ `devTools.enabled` and `devTools.fill` usage remains supported.
2
21
 
3
22
  ## 2.8.0 - 2026-08-08
4
23
 
package/README.md CHANGED
@@ -123,7 +123,7 @@ Changing the state updates the complete theme immediately. Use
123
123
  `stringifyNajmThemeConfig(theme)` when persisting it, and parse settings loaded
124
124
  from an API or local storage with `parseNajmThemeConfig` before applying them.
125
125
 
126
- ## Components
126
+ ## Components
127
127
 
128
128
  Import from `najm-kit`:
129
129
 
@@ -145,7 +145,42 @@ import { Form, FormInput, useNForm } from 'najm-kit';
145
145
  | Feedback | Alert, Badge, Progress, Spinner, Toast |
146
146
  | Layout | Card, Sheet, Dialog, Popover, DropdownMenu, Tabs |
147
147
  | Data | Table (NTable), StatCard, DetailList |
148
- | Overlays | Command palette, Tooltip, Toast |
148
+ | Overlays | Command palette, Tooltip, Toast |
149
+
150
+ ## Global form development tools
151
+
152
+ Enable schema-driven test values once on the full application provider. Every
153
+ `NForm` and `WizardForm` below it then fills from its Zod schema when F8 is
154
+ pressed; applications do not need a second provider or a form-fill helper.
155
+
156
+ ```tsx
157
+ import { NajmAppProvider } from "najm-kit/app";
158
+
159
+ <NajmAppProvider formDevTools>
160
+ <App />
161
+ </NajmAppProvider>;
162
+ ```
163
+
164
+ Pass a boolean to control it from application settings:
165
+
166
+ ```tsx
167
+ <NajmAppProvider formDevTools={formFillEnabled}>
168
+ <App />
169
+ </NajmAppProvider>
170
+ ```
171
+
172
+ Forms with live relation options can override only those fields. The provider
173
+ still owns enablement and Najm Kit still owns schema traversal and generation.
174
+
175
+ ```tsx
176
+ <NForm
177
+ schema={orderSchema}
178
+ devTools={{ overrides: { customerId: customerOptions } }}
179
+ onSubmit={saveOrder}
180
+ >
181
+ {/* fields */}
182
+ </NForm>
183
+ ```
149
184
 
150
185
  ## ImageInput and AvatarInput
151
186
 
@@ -472,9 +507,97 @@ const data = [
472
507
  />
473
508
  ```
474
509
 
475
- ### Server-backed combobox search
510
+ ### Server-backed combobox search
511
+
512
+ `ComboboxInput` and `FormInput type="combobox"` can delegate filtering to a
513
+ server by setting `shouldFilter={false}` and handling `onSearchChange`. Use
514
+ `loading` and `loadingMessage` while replacement options are being fetched.
515
+ Client-side filtering remains the default.
516
+
517
+ ## Person image fallbacks (`najm-kit/person-images`)
518
+
519
+ A framework-neutral, React-free subpath that resolves person-image fallbacks
520
+ for any application. The seven WebP illustrations are embedded as base64 data
521
+ URLs in the published bundle, so consumers do not need to copy package files
522
+ into `public/` or wire an asset server.
523
+
524
+ ```ts
525
+ import { getPersonImage } from "najm-kit/person-images";
526
+
527
+ const childSrc = getPersonImage({
528
+ image: child.image,
529
+ role: "child",
530
+ gender: child.gender,
531
+ });
532
+ ```
533
+
534
+ Built-in roles:
535
+
536
+ | Role | Default | Female | Male |
537
+ | -------- | ---------------- | --------------- | --------------- |
538
+ | `child` | male child art | female child | male child |
539
+ | `adult` | male adult art | female adult | male adult |
540
+ | `parent` | male parent art | female parent | male parent |
541
+ | `family` | neutral family | neutral family | neutral family |
542
+
543
+ Resolution precedence, for every call:
544
+
545
+ 1. A real `image` (anything that survives `resolveAvatarSrc`).
546
+ 2. A per-call `fallback` that is not blank and is not the `noavatar.png`
547
+ sentinel.
548
+ 3. The configured role's gender variant, or the role's required `default`
549
+ when the variant or the gender is missing.
550
+
551
+ The per-call `fallback` is treated like a real source: an empty string, a
552
+ blank trimmed value, or any `noavatar.png` path falls through to the role
553
+ default. The Kafil data is a worked example: children use `role: "child"`,
554
+ households use `role: "family"`, sponsors, staff, applicants, and delivery
555
+ staff use `role: "adult"`, and a household parent uses `role: "parent"`
556
+ after the family dashboard maps its relationship value (`mother`, `mère`,
557
+ `madre`, `أم`, …) to `F`, `M`, or `null` at the feature boundary.
558
+
559
+ ### Custom roles
560
+
561
+ `createPersonImageResolver` returns a typed resolver that accepts the
562
+ application's own role names. Unknown role strings fail type checking:
563
+
564
+ ```ts
565
+ import { createPersonImageResolver } from "najm-kit/person-images";
566
+
567
+ const getSmsPersonImage = createPersonImageResolver({
568
+ teacher: {
569
+ default: "/images/teachers/default.webp",
570
+ female: "/images/teachers/female.webp",
571
+ male: "/images/teachers/male.webp",
572
+ },
573
+ student: {
574
+ default: "/images/students/default.webp",
575
+ female: "/images/students/female.webp",
576
+ male: "/images/students/male.webp",
577
+ },
578
+ });
579
+
580
+ const teacherSrc = getSmsPersonImage({
581
+ image: teacher.image,
582
+ role: "teacher",
583
+ gender: teacher.gender,
584
+ });
585
+ ```
586
+
587
+ The factory merges custom definitions over the built-in map. A custom `child`
588
+ override replaces the built-in child art for that application alone — the
589
+ package itself is untouched, and other consumers keep their built-in
590
+ fallbacks.
591
+
592
+ Custom paths may be application-relative URLs, managed API URLs, CDN URLs,
593
+ or data URLs. najm-kit does not fetch, upload, authorize, or persist them.
594
+
595
+ ### Per-call fallback override
596
+
597
+ Every call accepts a `fallback`. It overrides the role default for that call
598
+ only, after a real `image` and before the role's gender variant:
599
+
600
+ ```ts
601
+ getPersonImage({ image: child.image, role: "child", gender: child.gender, fallback: child.placeholder });
602
+ ```
476
603
 
477
- `ComboboxInput` and `FormInput type="combobox"` can delegate filtering to a
478
- server by setting `shouldFilter={false}` and handling `onSearchChange`. Use
479
- `loading` and `loadingMessage` while replacement options are being fetched.
480
- Client-side filtering remains the default.
@@ -1,8 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { Translations } from 'najm-i18n';
3
- import { N as NBrandingInput } from '../NBrandingContext-dkADu8JS.js';
3
+ import { F as FormDevToolsOptions, N as NBrandingInput } from '../formFill-BcH-m9Kf.js';
4
4
  import { NajmNextUIProviderProps } from './next.js';
5
5
  import 'react';
6
+ import 'zod';
6
7
  import '../NajmUIProvider-BSbXaqak.js';
7
8
  import '../paginationLabels-dZLSNxfo.js';
8
9
 
@@ -13,6 +14,11 @@ interface NajmAppBranding {
13
14
  logoCollapsed?: string | null;
14
15
  }
15
16
  interface NajmAppProviderProps extends Omit<NajmNextUIProviderProps, 't'> {
17
+ /**
18
+ * Enables schema-driven form filling for every `NForm` and `WizardForm`.
19
+ * `true` uses F8; an options object can choose another shortcut.
20
+ */
21
+ formDevTools?: boolean | FormDevToolsOptions;
16
22
  /**
17
23
  * Catalog for `najm-i18n`. Supplying it mounts an `I18nProvider` and derives
18
24
  * the pagination labels from it, so `t` is not a prop here — the provider
@@ -99,6 +105,6 @@ interface NajmAppProviderProps extends Omit<NajmNextUIProviderProps, 't'> {
99
105
  * state machine of its own above this provider. The controlled `design` and
100
106
  * `branding` props still work for applications that already do.
101
107
  */
102
- declare function NajmAppProvider({ translations, initialLanguage, defaultLanguage, languageEndpoint, appName, initialBranding, ...props }: NajmAppProviderProps): react_jsx_runtime.JSX.Element;
108
+ declare function NajmAppProvider({ translations, initialLanguage, defaultLanguage, languageEndpoint, appName, initialBranding, formDevTools, ...props }: NajmAppProviderProps): react_jsx_runtime.JSX.Element;
103
109
 
104
110
  export { type NajmAppBranding, NajmAppProvider, type NajmAppProviderProps };
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { NBrandingStateProvider, NajmFormatProvider } from '../chunk-PCPMAEKP.mjs';
2
+ import { FormDevToolsProvider, NBrandingStateProvider, NajmFormatProvider } from '../chunk-GHMR45H3.mjs';
3
3
  import { NajmNextUIProvider } from '../chunk-IRFFSAO2.mjs';
4
4
  import '../chunk-USZUOJMK.mjs';
5
5
  import '../chunk-KVZACF4G.mjs';
@@ -72,6 +72,7 @@ function NajmAppProvider({
72
72
  languageEndpoint = DEFAULT_LANGUAGE_ENDPOINT,
73
73
  appName,
74
74
  initialBranding,
75
+ formDevTools,
75
76
  ...props
76
77
  }) {
77
78
  const persistLanguage = React.useCallback(
@@ -91,8 +92,10 @@ function NajmAppProvider({
91
92
  [languageEndpoint]
92
93
  );
93
94
  const seeded = appName ? { appName, ...initialBranding } : initialBranding;
94
- if (!translations) return /* @__PURE__ */ jsx(NajmAppNoI18n, { ...props, initialBranding: seeded });
95
- return /* @__PURE__ */ jsx(
95
+ if (!translations) {
96
+ return /* @__PURE__ */ jsx(FormDevToolsProvider, { value: formDevTools, children: /* @__PURE__ */ jsx(NajmAppNoI18n, { ...props, initialBranding: seeded }) });
97
+ }
98
+ return /* @__PURE__ */ jsx(FormDevToolsProvider, { value: formDevTools, children: /* @__PURE__ */ jsx(
96
99
  I18nProvider,
97
100
  {
98
101
  translations,
@@ -101,7 +104,7 @@ function NajmAppProvider({
101
104
  onLanguageChange: persistLanguage,
102
105
  children: /* @__PURE__ */ jsx(NajmAppUI, { ...props, initialBranding: seeded })
103
106
  }
104
- );
107
+ ) });
105
108
  }
106
109
 
107
110
  export { NajmAppProvider };
@@ -0,0 +1,12 @@
1
+ // src/lib/avatar.ts
2
+ var PLACEHOLDER_AVATAR = /(^|\/)noavatar\.png(?:$|[?#])/i;
3
+ function isPlaceholderAvatar(src) {
4
+ const trimmed = src?.trim() ?? "";
5
+ return !trimmed || PLACEHOLDER_AVATAR.test(trimmed);
6
+ }
7
+ function resolveAvatarSrc(src, fallback) {
8
+ const trimmed = src?.trim() ?? "";
9
+ return trimmed && !PLACEHOLDER_AVATAR.test(trimmed) ? trimmed : fallback;
10
+ }
11
+
12
+ export { isPlaceholderAvatar, resolveAvatarSrc };
@@ -0,0 +1,333 @@
1
+ import { useNajmPreferencesContext } from './chunk-USZUOJMK.mjs';
2
+ import { humanizeToken, DEFAULT_PLACEHOLDER, formatRelativeTime, formatTime, formatDateTime, formatDate, formatPercent, formatNumber, formatCurrency } from './chunk-JABLSOQN.mjs';
3
+ import * as React2 from 'react';
4
+ import { createContext, useContext, useMemo, useState, useCallback } from 'react';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ function normalizeBranding(input) {
8
+ if (!input) return {};
9
+ const value = {};
10
+ const expanded = input.logoExpanded ?? input.sidebarLogoExpandedPath;
11
+ const collapsed = input.logoCollapsed ?? input.sidebarLogoCollapsedPath;
12
+ if (input.appName !== void 0) value.appName = input.appName;
13
+ if (input.logoFallback !== void 0) value.logoFallback = input.logoFallback;
14
+ if (input.logoHref !== void 0) value.logoHref = input.logoHref;
15
+ if (expanded !== void 0) value.logoExpanded = expanded;
16
+ if (collapsed !== void 0) value.logoCollapsed = collapsed;
17
+ return value;
18
+ }
19
+ var NBrandingContext = createContext(null);
20
+ function useNBranding() {
21
+ return useContext(NBrandingContext);
22
+ }
23
+ function NBrandingProvider({
24
+ children,
25
+ appName,
26
+ logoExpanded,
27
+ logoCollapsed,
28
+ logoFallback,
29
+ logoHref
30
+ }) {
31
+ const value = useMemo(
32
+ () => ({ appName, logoExpanded, logoCollapsed, logoFallback, logoHref }),
33
+ [appName, logoExpanded, logoCollapsed, logoFallback, logoHref]
34
+ );
35
+ return /* @__PURE__ */ jsx(NBrandingContext.Provider, { value, children });
36
+ }
37
+ var NBrandingEditorContext = createContext(null);
38
+ function useNBrandingEditor() {
39
+ return useContext(NBrandingEditorContext);
40
+ }
41
+ function NBrandingStateProvider({
42
+ children,
43
+ branding,
44
+ initialBranding
45
+ }) {
46
+ const [state, setState] = useState(
47
+ () => normalizeBranding(initialBranding ?? branding)
48
+ );
49
+ const setBranding = useCallback((patch) => {
50
+ const marks = normalizeBranding(patch);
51
+ setState((current) => ({ ...current, ...marks }));
52
+ }, []);
53
+ const controlled = useMemo(
54
+ () => branding ? normalizeBranding(branding) : void 0,
55
+ [branding]
56
+ );
57
+ const resolved = controlled ?? state;
58
+ const editor = useMemo(
59
+ () => ({ branding: resolved, setBranding: branding ? noop : setBranding }),
60
+ [resolved, branding, setBranding]
61
+ );
62
+ return /* @__PURE__ */ jsx(NBrandingEditorContext.Provider, { value: editor, children: /* @__PURE__ */ jsx(NBrandingProvider, { ...resolved, children }) });
63
+ }
64
+ function noop() {
65
+ }
66
+
67
+ // src/components/form/formFill.ts
68
+ var CITIES = ["Casablanca", "Rabat", "Marrakesh", "Tangier"];
69
+ var SCHOOL_LEVELS = ["Primary", "Middle school", "Secondary school"];
70
+ var CLOTHING_SIZES = ["6 years", "8 years", "10 years", "12 years"];
71
+ function pick(values) {
72
+ if (values.length === 0) return void 0;
73
+ return values[Math.floor(Math.random() * values.length)];
74
+ }
75
+ function randomDigits(length) {
76
+ return Array.from({ length }, () => Math.floor(Math.random() * 10)).join("");
77
+ }
78
+ function randomToken(length = 8) {
79
+ const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
80
+ return Array.from(
81
+ { length },
82
+ () => alphabet[Math.floor(Math.random() * alphabet.length)]
83
+ ).join("");
84
+ }
85
+ function asZodLike(schema) {
86
+ return schema && typeof schema === "object" ? schema : void 0;
87
+ }
88
+ function definition(schema) {
89
+ const current = asZodLike(schema);
90
+ return current?._def ?? current?.def ?? {};
91
+ }
92
+ function schemaKind(schema) {
93
+ const currentDefinition = definition(schema);
94
+ const raw = currentDefinition.typeName ?? currentDefinition.type;
95
+ if (typeof raw !== "string") return "";
96
+ return raw.replace(/^Zod/, "").toLowerCase();
97
+ }
98
+ function unwrap(schema) {
99
+ const seen = /* @__PURE__ */ new Set();
100
+ let current = asZodLike(schema);
101
+ while (current && !seen.has(current)) {
102
+ seen.add(current);
103
+ const kind = schemaKind(current);
104
+ const currentDefinition = definition(current);
105
+ const inner = currentDefinition.innerType ?? currentDefinition.schema ?? (kind === "pipe" ? currentDefinition.out ?? currentDefinition.in : void 0);
106
+ if (!inner || typeof inner !== "object") break;
107
+ current = inner;
108
+ }
109
+ return current;
110
+ }
111
+ function objectShape(schema) {
112
+ const current = unwrap(schema);
113
+ if (schemaKind(current) !== "object") return null;
114
+ const shape = current?.shape ?? definition(current).shape;
115
+ return typeof shape === "function" ? shape() : shape ?? null;
116
+ }
117
+ function arrayElement(schema) {
118
+ const current = unwrap(schema);
119
+ if (schemaKind(current) !== "array") return null;
120
+ const currentDefinition = definition(current);
121
+ const element = current?.element ?? currentDefinition.element ?? currentDefinition.type;
122
+ return element && typeof element === "object" ? element : null;
123
+ }
124
+ function enumValues(schema) {
125
+ const current = unwrap(schema);
126
+ const currentDefinition = definition(current);
127
+ const kind = schemaKind(current);
128
+ if (kind === "literal") {
129
+ const values2 = currentDefinition.values;
130
+ if (Array.isArray(values2)) return values2;
131
+ return "value" in currentDefinition ? [currentDefinition.value] : [];
132
+ }
133
+ if (kind !== "enum" && kind !== "nativeenum") return [];
134
+ const entries = currentDefinition.entries;
135
+ if (entries && typeof entries === "object") return Object.values(entries);
136
+ const values = currentDefinition.values;
137
+ if (Array.isArray(values)) return values;
138
+ return Array.isArray(current?.options) ? [...current.options] : [];
139
+ }
140
+ function stringFormat(schema) {
141
+ const currentDefinition = definition(unwrap(schema));
142
+ if (typeof currentDefinition.format === "string") return currentDefinition.format;
143
+ const checks = Array.isArray(currentDefinition.checks) ? currentDefinition.checks : [];
144
+ for (const check of checks) {
145
+ if (!check || typeof check !== "object") continue;
146
+ const value = check;
147
+ const format = value.format ?? value.kind;
148
+ if (typeof format === "string") return format;
149
+ }
150
+ return "";
151
+ }
152
+ function resolveOverride(override, fieldName) {
153
+ if (typeof override === "function") return override(fieldName);
154
+ if (Array.isArray(override)) {
155
+ const value = pick(override);
156
+ if (value && typeof value === "object" && "value" in value) {
157
+ return value.value;
158
+ }
159
+ return value;
160
+ }
161
+ return override;
162
+ }
163
+ function localDateInput(date = /* @__PURE__ */ new Date()) {
164
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
165
+ }
166
+ function birthDate(child) {
167
+ const date = /* @__PURE__ */ new Date();
168
+ date.setFullYear(date.getFullYear() - (child ? 10 : 35));
169
+ return localDateInput(date);
170
+ }
171
+ function fieldValue(fieldName, schema, siblingFields) {
172
+ const key = fieldName.toLowerCase();
173
+ const current = unwrap(schema);
174
+ const kind = schemaKind(current);
175
+ const format = stringFormat(current);
176
+ const values = enumValues(current);
177
+ if (values.length) return pick(values);
178
+ if (kind === "boolean") return true;
179
+ if (kind === "number" || kind === "bigint") {
180
+ if (key.includes("sortorder")) return 1;
181
+ if (key.includes("quantity")) return 5;
182
+ return 10;
183
+ }
184
+ if (format === "uuid" || key.endsWith("uuid")) {
185
+ return "9cc2c93f-f545-4e07-9f77-f79f08a71dd5";
186
+ }
187
+ if (format === "email" || key.includes("email")) {
188
+ return `test-${randomToken()}@example.com`;
189
+ }
190
+ if (format === "url" || key === "image" || key.includes("imageurl") || key.endsWith("url")) {
191
+ return `https://picsum.photos/seed/${randomToken()}/800/600`;
192
+ }
193
+ if (key === "month") return `${localDateInput().slice(0, 7)}-01`;
194
+ if (key === "registrationdate") return localDateInput();
195
+ if (format === "date" || key.includes("dateofbirth")) {
196
+ const child = siblingFields.has("schoolLevel") || siblingFields.has("clothingSize");
197
+ return birthDate(child);
198
+ }
199
+ if (key.endsWith("id") || key.endsWith("by")) return "";
200
+ if (key === "name") {
201
+ if (siblingFields.has("sku")) return `Test product ${randomToken(4)}`;
202
+ if (siblingFields.has("slug")) return `Test category ${randomToken(4)}`;
203
+ return "Test User";
204
+ }
205
+ if (key.includes("legalname")) return "Test User";
206
+ if (key.includes("phone")) return `+2126${randomDigits(8)}`;
207
+ if (key.endsWith("cin")) return `AB${randomDigits(6)}`;
208
+ if (key.includes("address")) return `10 Test Street, ${pick(CITIES)}`;
209
+ if (key.includes("schoollevel")) return pick(SCHOOL_LEVELS);
210
+ if (key.includes("clothingsize")) return pick(CLOTHING_SIZES);
211
+ if (key.includes("shoesize")) return "36";
212
+ if (key.includes("relationship")) return "Legal guardian";
213
+ if (key.includes("activationtargetmad")) return "7500";
214
+ if (key === "slug") return `test-${randomToken(6)}`;
215
+ if (key === "sku") return `TEST-${randomToken(8).toUpperCase()}`;
216
+ if (key.includes("amountmad") || key.includes("pricemad") || key.includes("limitmad") || key.includes("targetmad")) {
217
+ return "100.00";
218
+ }
219
+ if (key.includes("reason")) return "Generated for form testing.";
220
+ if (key.includes("notes")) return "Generated form testing note.";
221
+ if (key.includes("description")) return "Generated description for form testing.";
222
+ if (key.includes("code")) return randomToken(8).toUpperCase();
223
+ return `Test ${fieldName}`;
224
+ }
225
+ function buildFormFill(schema, overrides = {}) {
226
+ const shape = objectShape(schema) ?? {};
227
+ const siblingFields = new Set(Object.keys(shape));
228
+ const output = {};
229
+ for (const [fieldName, fieldSchema] of Object.entries(shape)) {
230
+ if (Object.prototype.hasOwnProperty.call(overrides, fieldName)) {
231
+ output[fieldName] = resolveOverride(overrides[fieldName], fieldName);
232
+ continue;
233
+ }
234
+ const nestedShape = objectShape(fieldSchema);
235
+ if (nestedShape) {
236
+ output[fieldName] = buildFormFill(fieldSchema);
237
+ continue;
238
+ }
239
+ const element = arrayElement(fieldSchema);
240
+ if (element) {
241
+ output[fieldName] = objectShape(element) ? [buildFormFill(element)] : [fieldValue(fieldName, element, siblingFields)];
242
+ continue;
243
+ }
244
+ output[fieldName] = fieldValue(fieldName, fieldSchema, siblingFields);
245
+ }
246
+ return output;
247
+ }
248
+ var DEFAULT_FORM_DEV_TOOLS = {
249
+ enabled: false,
250
+ shortcut: "F8"
251
+ };
252
+ var FormDevToolsContext = createContext(
253
+ DEFAULT_FORM_DEV_TOOLS
254
+ );
255
+ function normalizeFormDevTools(value) {
256
+ if (typeof value === "boolean") {
257
+ return { ...DEFAULT_FORM_DEV_TOOLS, enabled: value };
258
+ }
259
+ if (!value) return DEFAULT_FORM_DEV_TOOLS;
260
+ return {
261
+ enabled: value.enabled ?? true,
262
+ shortcut: value.shortcut ?? DEFAULT_FORM_DEV_TOOLS.shortcut
263
+ };
264
+ }
265
+ function FormDevToolsProvider({
266
+ children,
267
+ value
268
+ }) {
269
+ const resolved = useMemo(
270
+ () => normalizeFormDevTools(value),
271
+ [value]
272
+ );
273
+ return /* @__PURE__ */ jsx(FormDevToolsContext.Provider, { value: resolved, children });
274
+ }
275
+ function useResolvedFormDevTools(schema, local) {
276
+ const global = useContext(FormDevToolsContext);
277
+ const options = typeof local === "object" ? local : void 0;
278
+ const enabled = typeof local === "boolean" ? local : options?.enabled ?? global.enabled;
279
+ const shortcut = options?.shortcut ?? global.shortcut;
280
+ const fill = options?.fill ?? (schema ? () => buildFormFill(schema, options?.overrides) : void 0);
281
+ return { enabled, shortcut, fill };
282
+ }
283
+ var NajmFormatContext = React2.createContext(
284
+ null
285
+ );
286
+ function NajmFormatProvider({
287
+ children,
288
+ locale,
289
+ currency,
290
+ timeZone,
291
+ placeholder = DEFAULT_PLACEHOLDER
292
+ }) {
293
+ const preferences = useNajmPreferencesContext();
294
+ const resolvedTimeZone = timeZone ?? preferences?.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
295
+ const value = React2.useMemo(() => {
296
+ const config = {
297
+ locale,
298
+ timeZone: resolvedTimeZone,
299
+ currency,
300
+ placeholder
301
+ };
302
+ return {
303
+ locale,
304
+ timeZone: resolvedTimeZone,
305
+ currency,
306
+ placeholder,
307
+ config,
308
+ money: (minorUnits) => formatCurrency(minorUnits, config),
309
+ number: (value_, options) => formatNumber(value_, config, options),
310
+ percent: (value_, digits) => formatPercent(value_, config, digits),
311
+ date: (value_, options) => formatDate(value_, config, options),
312
+ dateTime: (value_) => formatDateTime(value_, config),
313
+ time: (value_) => formatTime(value_, config),
314
+ relativeTime: (value_) => formatRelativeTime(value_, config),
315
+ humanize: humanizeToken
316
+ };
317
+ }, [locale, resolvedTimeZone, currency, placeholder]);
318
+ return /* @__PURE__ */ jsx(NajmFormatContext.Provider, { value, children });
319
+ }
320
+ function useNajmFormatContext() {
321
+ return React2.useContext(NajmFormatContext);
322
+ }
323
+ function useNajmFormat() {
324
+ const value = React2.useContext(NajmFormatContext);
325
+ if (!value) {
326
+ throw new Error(
327
+ "useNajmFormat must be rendered under a NajmFormatProvider or NajmAppProvider."
328
+ );
329
+ }
330
+ return value;
331
+ }
332
+
333
+ export { FormDevToolsProvider, NBrandingProvider, NBrandingStateProvider, NajmFormatProvider, buildFormFill, normalizeBranding, useNBranding, useNBrandingEditor, useNajmFormat, useNajmFormatContext, useResolvedFormDevTools };
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
+ import { ZodTypeAny, TypeOf } from 'zod';
3
4
 
4
5
  interface NBrandingValue {
5
6
  /** Used as the logo's `alt` when the logo does not set one. */
@@ -79,4 +80,18 @@ interface NBrandingStateProviderProps {
79
80
  /** `NBrandingProvider` with the marks held as state an editor can write. */
80
81
  declare function NBrandingStateProvider({ children, branding, initialBranding, }: Readonly<NBrandingStateProviderProps>): react_jsx_runtime.JSX.Element;
81
82
 
82
- export { type NBrandingInput as N, type NBrandingEditorValue as a, type NBrandingPayload as b, NBrandingProvider as c, NBrandingStateProvider as d, type NBrandingStateProviderProps as e, type NBrandingValue as f, useNBrandingEditor as g, normalizeBranding as n, useNBranding as u };
83
+ type FormFillOverride = unknown | readonly unknown[] | ((fieldName: string) => unknown);
84
+ type FormFillOverrides = Record<string, FormFillOverride>;
85
+ interface FormDevToolsOptions {
86
+ enabled?: boolean;
87
+ shortcut?: string;
88
+ }
89
+ interface FormDevToolsConfig<T extends ZodTypeAny = ZodTypeAny> extends FormDevToolsOptions {
90
+ fill?: () => Partial<TypeOf<T>>;
91
+ overrides?: FormFillOverrides;
92
+ }
93
+ type FormDevTools<T extends ZodTypeAny = ZodTypeAny> = boolean | FormDevToolsConfig<T>;
94
+ /** Build form-shaped test values from a Zod object schema. */
95
+ declare function buildFormFill<TSchema extends ZodTypeAny>(schema: TSchema, overrides?: FormFillOverrides): Partial<TypeOf<TSchema>>;
96
+
97
+ export { type FormDevToolsOptions as F, type NBrandingInput as N, type FormDevTools as a, type FormDevToolsConfig as b, type FormFillOverride as c, type FormFillOverrides as d, type NBrandingEditorValue as e, type NBrandingPayload as f, NBrandingProvider as g, NBrandingStateProvider as h, type NBrandingStateProviderProps as i, type NBrandingValue as j, buildFormFill as k, useNBrandingEditor as l, normalizeBranding as n, useNBranding as u };