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 +20 -1
- package/README.md +130 -7
- package/dist/adapters/app.d.ts +8 -2
- package/dist/adapters/app.mjs +7 -4
- package/dist/chunk-BEGOH366.mjs +12 -0
- package/dist/chunk-GHMR45H3.mjs +333 -0
- package/dist/{NBrandingContext-dkADu8JS.d.ts → formFill-BcH-m9Kf.d.ts} +16 -1
- package/dist/index.d.ts +11 -7
- package/dist/index.mjs +45 -21
- package/dist/person-images.d.ts +98 -0
- package/dist/person-images.mjs +86 -0
- package/package.json +6 -1
- package/dist/chunk-PCPMAEKP.mjs +0 -116
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.
|
package/dist/adapters/app.d.ts
CHANGED
|
@@ -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 '../
|
|
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 };
|
package/dist/adapters/app.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { NBrandingStateProvider, NajmFormatProvider } from '../chunk-
|
|
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)
|
|
95
|
-
|
|
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
|
-
|
|
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 };
|