@owlmeans/client-panel 0.1.18-rc.2 → 0.1.18-rc.21

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 (51) hide show
  1. package/README.md +60 -2
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/client-panel/SKILL.md +69 -5
  4. package/build/auth/index.d.ts +1 -0
  5. package/build/auth/index.d.ts.map +1 -1
  6. package/build/auth/index.js +1 -0
  7. package/build/auth/index.js.map +1 -1
  8. package/build/auth/login/index.d.ts +3 -0
  9. package/build/auth/login/index.d.ts.map +1 -0
  10. package/build/auth/login/index.js +2 -0
  11. package/build/auth/login/index.js.map +1 -0
  12. package/build/auth/login/model.d.ts +10 -0
  13. package/build/auth/login/model.d.ts.map +1 -0
  14. package/build/auth/login/model.js +84 -0
  15. package/build/auth/login/model.js.map +1 -0
  16. package/build/auth/login/types.d.ts +46 -0
  17. package/build/auth/login/types.d.ts.map +1 -0
  18. package/build/auth/login/types.js +2 -0
  19. package/build/auth/login/types.js.map +1 -0
  20. package/build/components/context.d.ts.map +1 -1
  21. package/build/components/form/component.js.map +1 -1
  22. package/build/components/form/context.d.ts.map +1 -1
  23. package/build/components/index.d.ts +1 -0
  24. package/build/components/index.d.ts.map +1 -1
  25. package/build/components/index.js +1 -0
  26. package/build/components/index.js.map +1 -1
  27. package/build/components/layout/helper.d.ts.map +1 -1
  28. package/build/components/nav/helper.d.ts +21 -0
  29. package/build/components/nav/helper.d.ts.map +1 -0
  30. package/build/components/nav/helper.js +111 -0
  31. package/build/components/nav/helper.js.map +1 -0
  32. package/build/components/nav/index.d.ts +3 -0
  33. package/build/components/nav/index.d.ts.map +1 -0
  34. package/build/components/nav/index.js +2 -0
  35. package/build/components/nav/index.js.map +1 -0
  36. package/build/components/nav/types.d.ts +76 -0
  37. package/build/components/nav/types.d.ts.map +1 -0
  38. package/build/components/nav/types.js +2 -0
  39. package/build/components/nav/types.js.map +1 -0
  40. package/build/components/status.d.ts.map +1 -1
  41. package/build/helper/form.d.ts.map +1 -1
  42. package/package.json +14 -13
  43. package/src/auth/index.ts +1 -1
  44. package/src/auth/login/index.ts +2 -0
  45. package/src/auth/login/model.ts +101 -0
  46. package/src/auth/login/types.ts +45 -0
  47. package/src/components/index.ts +1 -0
  48. package/src/components/nav/helper.ts +136 -0
  49. package/src/components/nav/index.ts +3 -0
  50. package/src/components/nav/types.ts +79 -0
  51. package/build/.gitkeep +0 -0
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # @owlmeans/client-panel
2
2
 
3
- Schema-driven React form components with react-hook-form integration, i18n, and action buttons.
3
+ Schema-driven React form components with react-hook-form integration, i18n, action buttons, and the
4
+ headless navigation model.
4
5
 
5
6
  ## Overview
6
7
 
@@ -9,6 +10,7 @@ Schema-driven React form components with react-hook-form integration, i18n, and
9
10
  - `InputCtrl` — labeled input field component backed by react-hook-form
10
11
  - `useFormRef()` — hook to get a ref for programmatic form operations
11
12
  - `FormOnSubmit` — type for form submission handler functions
13
+ - `usePanelNav()` — the model behind a two-layer navigation menu (no JSX)
12
14
  - Platform-agnostic: used by React web and React Native frontends
13
15
 
14
16
  ## Installation
@@ -29,7 +31,7 @@ function CreateProjectForm() {
29
31
  const formRef = useFormRef()
30
32
 
31
33
  const onSubmit: FormOnSubmit<CreateProject> = async (data) => {
32
- await ctx.module<ClientModule<Project>>('project-create').call({ body: data })
34
+ await ctx.entrypoint<ClientEntrypoint<Project>>('project-create').call({ body: data })
33
35
  }
34
36
 
35
37
  return (
@@ -81,6 +83,62 @@ type FormOnSubmit<T> = (data: T) => void | Promise<void>
81
83
 
82
84
  Derives default form values from an AJV schema's `default` fields.
83
85
 
86
+ ## Navigation model
87
+
88
+ Navigation is declared as data and rendered by the platform package
89
+ (`@owlmeans/web-panel` ships `NavLayout` / `TopNav` / `SideNav` / `Footer`). This package holds only
90
+ the model, so both levels of the menu agree on what is active.
91
+
92
+ ```typescript
93
+ import { usePanelNav } from '@owlmeans/client-panel'
94
+ import type { PanelNavConfig } from '@owlmeans/client-panel'
95
+
96
+ const config: PanelNavConfig = {
97
+ sections: [
98
+ { name: 'home', label: 'Home', items: [{ alias: HOME, label: 'Overview' }] },
99
+ { name: 'demo', items: [{ alias: web.session }, { alias: web.about }] },
100
+ ],
101
+ }
102
+
103
+ const model = usePanelNav(config)
104
+ ```
105
+
106
+ ### Types
107
+
108
+ - `PanelNavItem` — `{ alias, label?, Icon?, hidden? }`; one screen, addressed by entrypoint alias,
109
+ never by URL.
110
+ - `PanelNavSection` — `{ name, label?, items, hidden? }`; the first menu level. Pressing it goes to
111
+ the first visible item.
112
+ - `PanelNavConfig` — `{ sections }`.
113
+ - `PanelNavLink` — `{ alias? | href?, label?, open? }`; a footer link.
114
+ - `NavTranslate` — `(key, defaultValue) => string`.
115
+
116
+ ### `usePanelNav(config): PanelNavModel`
117
+
118
+ Returns `sections` (with `hidden` filtered out), `current` (the active screen's alias or null),
119
+ `active` (its section), `showSide`, `isSectionActive` / `isItemActive`, `goSection` / `goItem`
120
+ (each returning a handler), and `hrefOf(target)`.
121
+
122
+ - `showSide` is false when the active section holds a single screen — that is where the "one screen,
123
+ no second level" rule lives; a renderer asks the model rather than counting items.
124
+ - The current screen resolves from the router's `location.state.alias` when present, and otherwise
125
+ from the pathname matched against entrypoint paths — exact first, then longest prefix.
126
+ Both are needed: `state` is `window.history.state`, so it is empty on a hard load or deep link. A
127
+ screen listed in no section finds its section by walking its declared `parent` upward.
128
+ - `hrefOf` resolves a real URL synchronously so a menu entry can be a proper link (focusable,
129
+ keyboard-operable, openable in a new tab). It returns `undefined` for a path carrying route
130
+ parameters.
131
+
132
+ ### `resolveNavLabel(translate, label, key, alias)`
133
+
134
+ Resolves a label as literal `label` → `translate(key, defaultNavLabel(alias))` → the humanized
135
+ alias. `translate` always reaches a component as a **prop**, defaulting to `defaultNavTranslate`
136
+ (which returns the fallback) — a menu must never read an i18n context implicitly, because an app
137
+ mounted without an i18n provider throws inside render and blanks the page. `defaultNavLabel`
138
+ humanizes the last alias segment
139
+ (`my-app:web:user-list` → `User list`). Default key families are `nav.<section>` and
140
+ `modules.<alias>`.
141
+
84
142
  ## Related Packages
85
143
 
86
144
  - [`@owlmeans/client`](../client) — `useContext`, `useNavigate` used within form components
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/client-panel",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.513Z",
4
+ "version": "0.1.18-rc.21",
5
+ "generatedAt": "2026-09-01T16:49:00.429Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: client-panel
3
- description: How to use @owlmeans/client-panel — reusable cross-platform UI panel + form components (auth screens, layouts) shared by web-panel and native-panel. Auto-invoked when importing panel components.
3
+ description: How to use @owlmeans/client-panel — reusable cross-platform UI panel + form components (auth screens, layouts) and the headless navigation model (usePanelNav) shared by web-panel and native-panel. Auto-invoked when importing panel components or building a navigation menu.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,20 +8,28 @@ user-invocable: false
8
8
  # @owlmeans/client-panel
9
9
 
10
10
  **Layer:** Client
11
- **Install:** `"@owlmeans/client-panel": "^0.1.18-rc.0"` in `dependencies`
11
+ **Install:** `"@owlmeans/client-panel": "^0.1.18-rc.21"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
15
15
  | Export | Description |
16
16
  |--------|-------------|
17
17
  | `components` submodule | Cross-platform panel/form components |
18
+ | `usePanelNav(config)` | Headless navigation model behind a two-layer menu |
19
+ | `resolveNavLabel`, `defaultNavLabel`, `defaultNavTranslate` | Label resolution helpers |
20
+ | `PanelNavConfig`, `PanelNavSection`, `PanelNavItem`, `PanelNavLink`, `PanelNavModel`, `NavTranslate` | Navigation types |
18
21
  | `helpers` submodule | Layout / form helpers |
19
22
 
20
23
  ## Subpath Exports
21
24
 
22
- - `./auth` — auth-related panel components
25
+ - `./auth` — auth-related panel components, plus `useLoginMethods`
23
26
  - `./auth/plugins` — pluggable auth panel pieces
24
27
 
28
+ `useLoginMethods` is the headless model behind a sign-in screen: which methods are offered, whether
29
+ the terms are confirmed, what the credit line says, and a `select` that is deliberately NOT async
30
+ (the flow may have to open a window inside the click). The rendering lives in `web-panel` — the
31
+ same split the form and navigation models already use. See `login-methods`.
32
+
25
33
  ## Usage
26
34
 
27
35
  ```typescript
@@ -29,9 +37,65 @@ import { Panel } from '@owlmeans/client-panel'
29
37
  import { LoginPanel } from '@owlmeans/client-panel/auth'
30
38
  ```
31
39
 
32
- `@owlmeans/web-panel` re-exports these and provides the Material-UI implementations.
40
+ `@owlmeans/web-panel` re-exports these and provides the rendered implementations.
41
+
42
+ ## Navigation model — `usePanelNav`
43
+
44
+ The **model lives here, the JSX lives in the platform package** — the same split the form and panel
45
+ components already use. `usePanelNav` is headless and cross-platform; `@owlmeans/web-panel` renders
46
+ it as `TopNav`/`SideNav`/`NavLayout`. Never put a rendered menu in this package, and never
47
+ re-derive active state in a renderer.
48
+
49
+ Navigation is declared as data. A **section** is the first menu level; its **items** are the screens
50
+ the second level offers while that section is active. An item addresses a frontend entrypoint by
51
+ `alias` and never carries a URL — the router resolves it, so a path that changes shape stays correct
52
+ everywhere it is rendered.
53
+
54
+ ```ts
55
+ import { usePanelNav } from '@owlmeans/client-panel'
56
+ import type { PanelNavConfig } from '@owlmeans/client-panel'
57
+
58
+ const config: PanelNavConfig = {
59
+ sections: [
60
+ { name: 'home', label: 'Home', items: [{ alias: HOME, label: 'Overview' }] },
61
+ { name: 'demo', items: [{ alias: web.session }, { alias: web.about }] },
62
+ ],
63
+ }
64
+
65
+ const model = usePanelNav(config)
66
+ ```
67
+
68
+ `PanelNavModel` carries `sections` (with `hidden` filtered out), `current` (the active screen's
69
+ alias, or null), `active` (its section), `showSide`, `isSectionActive` / `isItemActive`,
70
+ `goSection` / `goItem` (each returning a handler), and `hrefOf`.
71
+
72
+ - **`showSide` owns the one-screen rule.** It is false when the active section holds a single
73
+ screen — a second level offering the page you are already on is noise. A renderer asks the model;
74
+ it does not count items itself.
75
+ - **Label resolution never touches i18n implicitly.** `NavTranslate` is a `(key, defaultValue) =>
76
+ string` **prop**, defaulting to `defaultNavTranslate`, which returns the fallback. An app mounted
77
+ without an i18n provider (`renderApp` from `@owlmeans/web-client` mounts none) crashes if a menu
78
+ reaches for the panel i18n context: the hook dereferences `i18n.options` on the empty object
79
+ `react-i18next` returns without an instance, and a throw inside render blanks the whole app.
80
+ `resolveNavLabel(translate, label, key, alias)` applies the order — literal `label` →
81
+ `translate(key, humanized)` → `defaultNavLabel(alias)`, which humanizes the last alias segment
82
+ (`my-app:web:user-list` → `User list`). Default key families: `nav.<name>` for sections,
83
+ `modules.<alias>` for items and footer links.
84
+ - **Resolving the current screen needs two sources.** The router's `location.state.alias` is
85
+ authoritative but is `window.history.state`, so it is null until the first in-app navigation — on
86
+ a hard page load or a deep link a menu keyed on it alone highlights nothing, on exactly the entry
87
+ that matters most. The pathname is the fallback: entrypoint paths are matched exactly
88
+ first, then by longest prefix, so a detail screen under a listed one still belongs to its section.
89
+ A screen listed in no section resolves its section by walking `route.route.parent` upward.
90
+ - **`hrefOf` gives a menu entry a real URL**, resolved synchronously from
91
+ `context.entrypoint(alias).path()` — an entrypoint composes its path from its own declaration and
92
+ its ancestors', so matching one is a lookup rather than a guess. It returns undefined for a path
93
+ carrying route parameters (`:id`) — there is no honest URL for a screen whose address is not
94
+ known yet. An alias the app never elevated resolves to null and is skipped rather than taking the
95
+ menu down.
33
96
 
34
97
  ## Depends On
35
98
 
36
- - `@owlmeans/client`, `@owlmeans/client-auth`, `@owlmeans/client-i18n`
99
+ - `@owlmeans/client`, `@owlmeans/client-i18n`, `@owlmeans/client-entrypoint`, `@owlmeans/client-route`,
100
+ `@owlmeans/entrypoint`, `@owlmeans/error`
37
101
  - `react` (peer)
@@ -1,2 +1,3 @@
1
1
  export * from './i18n.js';
2
+ export * from './login/index.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA"}
@@ -1,2 +1,3 @@
1
1
  export * from './i18n.js';
2
+ export * from './login/index.js';
2
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export type * from './types.js';
2
+ export * from './model.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/login/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from './model.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/login/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA"}
@@ -0,0 +1,10 @@
1
+ import type { LoginMethodsModel, UseLoginMethodsOptions } from './types.js';
2
+ /**
3
+ * Everything a sign-in screen needs, with no opinion about how it looks.
4
+ *
5
+ * Headless and cross-platform, like `usePanelNav`: the rules about what may be offered, what must
6
+ * be confirmed first and what the credit line says are the same on every platform, and only the
7
+ * rendering differs. A renderer that re-derives any of them will drift from the one that does not.
8
+ */
9
+ export declare const useLoginMethods: (opts?: UseLoginMethodsOptions) => LoginMethodsModel;
10
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/auth/login/model.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAE3E;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,sBAAsB,KAAG,iBAmF/D,CAAA"}
@@ -0,0 +1,84 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ import { useContext, useNavigate } from '@owlmeans/client';
3
+ import { acceptTerms, primaryLoginMethod, resolveCredit, resolveTerms, termsAccepted, LoginOutcome, LOGIN_SERVICE, } from '@owlmeans/client-auth/login';
4
+ /**
5
+ * Everything a sign-in screen needs, with no opinion about how it looks.
6
+ *
7
+ * Headless and cross-platform, like `usePanelNav`: the rules about what may be offered, what must
8
+ * be confirmed first and what the credit line says are the same on every platform, and only the
9
+ * rendering differs. A renderer that re-derives any of them will drift from the one that does not.
10
+ */
11
+ export const useLoginMethods = (opts) => {
12
+ const context = useContext();
13
+ const nav = useNavigate();
14
+ const login = context.service(LOGIN_SERVICE);
15
+ const cfg = opts?.config ?? context.cfg.security?.auth?.login;
16
+ const brand = context.cfg.brand;
17
+ const resolved = useMemo(() => resolveTerms(opts?.terms ?? cfg?.terms), [opts?.terms, cfg]);
18
+ const credit = useMemo(() => resolveCredit(cfg?.credit, brand, context.cfg.service), [cfg, brand, context]);
19
+ const [accepted, setAccepted] = useState(() => termsAccepted(resolved));
20
+ const [attempted, setAttempted] = useState(false);
21
+ const [busy, setBusy] = useState(null);
22
+ const [outcome, setOutcome] = useState(null);
23
+ const [error, setError] = useState(null);
24
+ const env = login.env();
25
+ const all = login.methods({
26
+ context, env,
27
+ navigate: (alias, params) => { nav.go(alias, params != null ? { params } : undefined); },
28
+ });
29
+ const methods = typeof opts?.methods === 'function'
30
+ ? opts.methods(all)
31
+ : opts?.methods ?? all;
32
+ const blocked = resolved != null && resolved.required && !accepted;
33
+ const select = useCallback((method) => {
34
+ if (blocked) {
35
+ // Nothing starts, and no window opens. The screen renders the reason instead — which is why
36
+ // a control must never be `disabled`: a disabled control swallows the click and says nothing.
37
+ setAttempted(true);
38
+ return;
39
+ }
40
+ setBusy(method.id);
41
+ setError(null);
42
+ void method.start({
43
+ context, env,
44
+ navigate: (alias, params) => { nav.go(alias, params != null ? { params } : undefined); },
45
+ }).then(result => {
46
+ setOutcome(result);
47
+ // `Redirected` means the browser is leaving; keeping the control busy is honest there.
48
+ if (result !== LoginOutcome.Redirected) {
49
+ setBusy(null);
50
+ }
51
+ }).catch((e) => {
52
+ setError(e.message);
53
+ setOutcome(LoginOutcome.Failed);
54
+ setBusy(null);
55
+ });
56
+ }, [blocked, context, env, nav]);
57
+ const accept = useCallback((value) => {
58
+ setAccepted(value);
59
+ setAttempted(false);
60
+ acceptTerms(resolved, value);
61
+ }, [resolved]);
62
+ return {
63
+ methods,
64
+ primary: primaryLoginMethod(methods),
65
+ terms: {
66
+ required: resolved?.required ?? false,
67
+ accepted: resolved == null ? true : accepted,
68
+ attempted,
69
+ urls: {
70
+ terms: resolved?.terms ?? '',
71
+ privacy: resolved?.privacy ?? '',
72
+ ...(resolved?.cookies != null ? { cookies: resolved.cookies } : {}),
73
+ },
74
+ accept,
75
+ },
76
+ credit,
77
+ blocked,
78
+ busy,
79
+ outcome,
80
+ error,
81
+ select,
82
+ };
83
+ };
84
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/auth/login/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { LoginMethod, LoginOutcome } from '@owlmeans/client-auth/login';
2
+ import type { LoginScreenConfig, LoginTermsConfig } from '@owlmeans/config';
3
+ export interface UseLoginMethodsOptions {
4
+ /** Overrides the configuration the context carries. */
5
+ config?: LoginScreenConfig;
6
+ terms?: LoginTermsConfig | false;
7
+ /** Replace or reorder what the resolver produced. */
8
+ methods?: LoginMethod[] | ((methods: LoginMethod[]) => LoginMethod[]);
9
+ }
10
+ export interface LoginTermsModel {
11
+ required: boolean;
12
+ accepted: boolean;
13
+ /** A blocked selection was attempted — render the explanation. */
14
+ attempted: boolean;
15
+ urls: {
16
+ terms: string;
17
+ privacy: string;
18
+ cookies?: string;
19
+ };
20
+ accept: (value: boolean) => void;
21
+ }
22
+ export interface LoginCreditModel {
23
+ poweredBy: boolean;
24
+ line: string | null;
25
+ }
26
+ export interface LoginMethodsModel {
27
+ methods: LoginMethod[];
28
+ /** The one a screen highlights and focuses. It is never started automatically. */
29
+ primary: LoginMethod | null;
30
+ terms: LoginTermsModel;
31
+ credit: LoginCreditModel;
32
+ /** True while the terms have not been confirmed and confirming them is required. */
33
+ blocked: boolean;
34
+ /** Id of the method currently starting. */
35
+ busy: string | null;
36
+ outcome: LoginOutcome | null;
37
+ error: string | null;
38
+ /**
39
+ * Start a method.
40
+ *
41
+ * NOT async, and it awaits nothing before delegating — the flow may need to open a window, and a
42
+ * window opened after the click has finished being handled is eaten by the popup blocker.
43
+ */
44
+ select: (method: LoginMethod) => void;
45
+ }
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/login/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAE3E,MAAM,WAAW,sBAAsB;IACrC,uDAAuD;IACvD,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,KAAK,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAA;IAChC,qDAAqD;IACrD,OAAO,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,CAAC,CAAA;CACtE;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;IACjB,kEAAkE;IAClE,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1D,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB,kFAAkF;IAClF,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,KAAK,EAAE,eAAe,CAAA;IACtB,MAAM,EAAE,gBAAgB,CAAA;IACxB,oFAAoF;IACpF,OAAO,EAAE,OAAO,CAAA;IAChB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;CACtC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/auth/login/types.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/components/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAiB,SAAS,EAAuB,MAAM,uBAAuB,CAAA;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,aAAa,CAK1C,CAAA;AAED,eAAO,MAAM,cAAc,qBAAsD,CAAA;AAEjF,eAAO,MAAM,YAAY,UAAW,MAAM,aAAa,SAAS,CAAC,MAAM,CAAC,0DASvE,CAAA;AAED,eAAO,MAAM,aAAa,SAAU,MAAM,UAAU,cAAc,uBAajE,CAAA"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/components/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAiB,SAAS,EAAuB,MAAM,uBAAuB,CAAA;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,aAAa,CAK1C,CAAA;AAED,eAAO,MAAM,cAAc,qBAAsD,CAAA;AAEjF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,EAAE,WAAW,SAAS,CAAC,MAAM,CAAC,0DASvE,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,EAAE,QAAQ,cAAc,uBAajE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"component.js","sourceRoot":"","sources":["../../../src/components/form/component.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"component.js","sourceRoot":"","sources":["../../../src/components/form/component.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/components/form/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAI9C,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,iBAAiB,CAAC,YAAY,CAAC,CACa,CAAA;AAEzE,eAAO,MAAM,oBAAoB,oBAAoD,CAAA;AAErF,eAAO,MAAM,WAAW,6DAQvB,CAAA;AAED,eAAO,MAAM,YAAY,SAAU,MAAM,UAAU,UAAU,uBAa5D,CAAA"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/components/form/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAI9C,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,iBAAiB,CAAC,YAAY,CAAC,CACa,CAAA;AAEzE,eAAO,MAAM,oBAAoB,oBAAoD,CAAA;AAErF,eAAO,MAAM,WAAW,6DAQvB,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,QAAQ,UAAU,uBAa5D,CAAA"}
@@ -1,6 +1,7 @@
1
1
  export type * from './types.js';
2
2
  export * from './form/index.js';
3
3
  export * from './layout/index.js';
4
+ export * from './nav/index.js';
4
5
  export * from './context.js';
5
6
  export * from './consts.js';
6
7
  export * from './status.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAE/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAE/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
@@ -1,5 +1,6 @@
1
1
  export * from './form/index.js';
2
2
  export * from './layout/index.js';
3
+ export * from './nav/index.js';
3
4
  export * from './context.js';
4
5
  export * from './consts.js';
5
6
  export * from './status.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAEA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAEA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../../src/components/layout/helper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAO3D,eAAO,MAAM,cAAc,GAAI,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,eAAe,GAAG,eAAe,OAAK,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAK3G,CAAA;AAED,eAAO,MAAM,cAAc,UAAW,MAAM,UAAU,MAAM,KAAG,MAM9D,CAAA;AAED,eAAO,MAAM,kBAAkB,UAAW,MAAM,WAA0C,CAAA"}
1
+ {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../../src/components/layout/helper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAO3D,eAAO,MAAM,cAAc,GAAI,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,eAAe,GAAG,eAAe,OAAK,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAK3G,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAG,MAM9D,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WAA0C,CAAA"}
@@ -0,0 +1,21 @@
1
+ import type { NavTranslate, PanelNavConfig, PanelNavModel } from './types.js';
2
+ /** Default resolver: no i18n, the caller's fallback wins. See {@link NavTranslate}. */
3
+ export declare const defaultNavTranslate: NavTranslate;
4
+ /**
5
+ * Turn an alias into something readable — `my-app:web:user-list` becomes `User list`.
6
+ * The last segment is the meaningful one; the prefixes address the app, not the screen.
7
+ */
8
+ export declare const defaultNavLabel: (alias: string) => string;
9
+ export declare const resolveNavLabel: (translate: NavTranslate, label: string | undefined, key: string, alias?: string) => string;
10
+ /**
11
+ * The navigation model behind both menus.
12
+ *
13
+ * Resolving the CURRENT screen has two sources, and both are needed. The router carries the
14
+ * alias in `location.state`, which is authoritative — but `state` is `window.history.state`,
15
+ * so it is null until the first in-app navigation: on a hard page load or a deep link there
16
+ * is nothing there, and a menu keyed on it alone highlights nothing on exactly the entry that
17
+ * matters most. The pathname is the fallback: an entrypoint computes its path from its own
18
+ * declaration and its ancestors', so matching them is a lookup, not a guess.
19
+ */
20
+ export declare const usePanelNav: (config: PanelNavConfig) => PanelNavModel;
21
+ //# sourceMappingURL=helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../../src/components/nav/helper.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAgB,aAAa,EAAmB,MAAM,YAAY,CAAA;AAE5G,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,EAAE,YAAmD,CAAA;AAErF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,MAK/C,CAAA;AAED,eAAO,MAAM,eAAe,GAC1B,WAAW,YAAY,EAAE,OAAO,MAAM,GAAG,SAAS,EAAE,KAAK,MAAM,EAAE,QAAQ,MAAM,KAC9E,MAAgE,CAAA;AAQnE;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,cAAc,KAAG,aA6FpD,CAAA"}
@@ -0,0 +1,111 @@
1
+ import { useMemo } from 'react';
2
+ import { useContext, useNavigate } from '@owlmeans/client';
3
+ /** Default resolver: no i18n, the caller's fallback wins. See {@link NavTranslate}. */
4
+ export const defaultNavTranslate = (_key, defaultValue) => defaultValue;
5
+ /**
6
+ * Turn an alias into something readable — `my-app:web:user-list` becomes `User list`.
7
+ * The last segment is the meaningful one; the prefixes address the app, not the screen.
8
+ */
9
+ export const defaultNavLabel = (alias) => {
10
+ const segment = alias.split(/[:.]/).filter(part => part !== '').pop() ?? alias;
11
+ const words = segment.replace(/[-_]+/g, ' ').trim();
12
+ return words.charAt(0).toUpperCase() + words.slice(1);
13
+ };
14
+ export const resolveNavLabel = (translate, label, key, alias) => label ?? translate(key, defaultNavLabel(alias ?? key));
15
+ const visibleItems = (section) => section.items.filter(item => item.hidden !== true);
16
+ const normalizePath = (path) => path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path;
17
+ /**
18
+ * The navigation model behind both menus.
19
+ *
20
+ * Resolving the CURRENT screen has two sources, and both are needed. The router carries the
21
+ * alias in `location.state`, which is authoritative — but `state` is `window.history.state`,
22
+ * so it is null until the first in-app navigation: on a hard page load or a deep link there
23
+ * is nothing there, and a menu keyed on it alone highlights nothing on exactly the entry that
24
+ * matters most. The pathname is the fallback: an entrypoint computes its path from its own
25
+ * declaration and its ancestors', so matching them is a lookup, not a guess.
26
+ */
27
+ export const usePanelNav = (config) => {
28
+ const context = useContext();
29
+ const nav = useNavigate();
30
+ const location = context.router().useLocation();
31
+ const state = location.state;
32
+ const pathname = location.pathname;
33
+ return useMemo(() => {
34
+ const sections = config.sections
35
+ .filter(section => section.hidden !== true)
36
+ .map(section => ({ ...section, items: visibleItems(section) }));
37
+ const pathOf = (alias) => {
38
+ try {
39
+ return normalizePath(context.entrypoint(alias).path());
40
+ }
41
+ catch {
42
+ // An alias the app never elevated addresses nothing — it cannot be the current screen,
43
+ // and it must not take the menu down with it.
44
+ return null;
45
+ }
46
+ };
47
+ const all = sections.flatMap(section => section.items);
48
+ let current = state?.alias ?? null;
49
+ if (current == null) {
50
+ const here = normalizePath(pathname);
51
+ const exact = all.find(item => pathOf(item.alias) === here);
52
+ if (exact != null) {
53
+ current = exact.alias;
54
+ }
55
+ else {
56
+ // Longest prefix: a detail screen under a listed one still belongs to its section.
57
+ const prefixed = all
58
+ .map(item => ({ item, path: pathOf(item.alias) }))
59
+ .filter((entry) => entry.path != null && entry.path !== '/' && here.startsWith(`${entry.path}/`))
60
+ .sort((a, b) => b.path.length - a.path.length)[0];
61
+ current = prefixed?.item.alias ?? null;
62
+ }
63
+ }
64
+ const sectionOf = (alias) => alias == null ? null : sections.find(section => section.items.some(item => item.alias === alias)) ?? null;
65
+ let active = sectionOf(current);
66
+ if (active == null && current != null) {
67
+ // The current screen is not listed anywhere — walk up to the ancestor that is, so a
68
+ // nested screen keeps its section (and its side menu) rather than clearing the chrome.
69
+ let alias = current;
70
+ const seen = new Set();
71
+ while (alias != null && !seen.has(alias)) {
72
+ seen.add(alias);
73
+ try {
74
+ alias = context.entrypoint(alias).route.route.parent ?? null;
75
+ }
76
+ catch {
77
+ alias = null;
78
+ }
79
+ const found = sectionOf(alias);
80
+ if (found != null) {
81
+ active = found;
82
+ break;
83
+ }
84
+ }
85
+ }
86
+ const isItemActive = (item) => item.alias === current;
87
+ const isSectionActive = (section) => active != null && section.name === active.name;
88
+ return {
89
+ sections,
90
+ current,
91
+ active,
92
+ showSide: active != null && active.items.length > 1,
93
+ isSectionActive,
94
+ isItemActive,
95
+ goSection: section => {
96
+ const first = section.items[0];
97
+ return first != null ? nav.press(first.alias) : () => { };
98
+ },
99
+ goItem: item => nav.press(item.alias),
100
+ hrefOf: target => {
101
+ const alias = 'alias' in target ? target.alias : target.items[0]?.alias;
102
+ if (alias == null) {
103
+ return undefined;
104
+ }
105
+ const path = pathOf(alias);
106
+ return path == null || path.includes(':') ? undefined : path;
107
+ },
108
+ };
109
+ }, [config, pathname, state?.alias, context, nav]);
110
+ };
111
+ //# sourceMappingURL=helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../../../src/components/nav/helper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export type * from './types.js';
2
+ export * from './helper.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/nav/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAE/B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from './helper.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/nav/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA"}
@@ -0,0 +1,76 @@
1
+ import type { ComponentType } from 'react';
2
+ /**
3
+ * One screen in the navigation — the second menu level.
4
+ *
5
+ * `alias` addresses a frontend entrypoint, so an item never carries a URL: the router
6
+ * resolves it, and a path that changes shape stays correct everywhere it is rendered.
7
+ */
8
+ export interface PanelNavItem {
9
+ alias: string;
10
+ /** Literal label. Absent, the label is resolved through `translate` and falls back to a humanized alias. */
11
+ label?: string;
12
+ Icon?: ComponentType<{
13
+ className?: string;
14
+ }>;
15
+ hidden?: boolean;
16
+ }
17
+ /**
18
+ * One section — the first menu level. Its items are the screens the side menu offers
19
+ * while the section is active.
20
+ */
21
+ export interface PanelNavSection {
22
+ /** Stable key. Also the default translation key (`nav.<name>`). */
23
+ name: string;
24
+ label?: string;
25
+ /** Ordered. The first visible item is where pressing the section navigates. */
26
+ items: PanelNavItem[];
27
+ hidden?: boolean;
28
+ }
29
+ export interface PanelNavConfig {
30
+ sections: PanelNavSection[];
31
+ }
32
+ /** A footer link: exactly one of `alias` (an entrypoint) or `href` (anything else). */
33
+ export interface PanelNavLink {
34
+ alias?: string;
35
+ href?: string;
36
+ label?: string;
37
+ /** Open in a new tab. */
38
+ open?: boolean;
39
+ }
40
+ /**
41
+ * Resolves a label from a translation key.
42
+ *
43
+ * It is a PROP, never an implicit context read: an app that mounts without an i18n provider
44
+ * (`renderApp` from `@owlmeans/web-client` does) must still render a menu, and reaching for
45
+ * the panel i18n context there throws inside the render tree and blanks the whole app.
46
+ * The default returns the fallback, so literal labels work with no i18n at all.
47
+ */
48
+ export interface NavTranslate {
49
+ (key: string, defaultValue: string): string;
50
+ }
51
+ export interface PanelNavModel {
52
+ /** Sections and items with `hidden` filtered out. */
53
+ sections: PanelNavSection[];
54
+ /** Alias of the screen currently rendered, when it could be resolved. */
55
+ current: string | null;
56
+ active: PanelNavSection | null;
57
+ /**
58
+ * Whether the side menu carries anything worth showing — false when the active section
59
+ * holds a single screen, which is the whole point: one screen needs no second level.
60
+ */
61
+ showSide: boolean;
62
+ isSectionActive: (section: PanelNavSection) => boolean;
63
+ isItemActive: (item: PanelNavItem) => boolean;
64
+ goSection: (section: PanelNavSection) => () => void;
65
+ goItem: (item: PanelNavItem) => () => void;
66
+ /**
67
+ * The resolved URL of a section's landing screen, when it has one.
68
+ *
69
+ * A menu entry navigates through `nav.press`, but it still has to BE a link: an `<a>`
70
+ * without `href` is not focusable, does not answer the keyboard, and cannot be opened in a
71
+ * new tab. Undefined for a path carrying route parameters — there is no honest URL to show
72
+ * for a screen whose address is not known yet.
73
+ */
74
+ hrefOf: (target: PanelNavItem | PanelNavSection) => string | undefined;
75
+ }
76
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/nav/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,4GAA4G;IAC5G,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,EAAE,CAAA;CAC5B;AAED,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,yBAAyB;IACzB,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;CAC5C;AAED,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,yEAAyE;IACzE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,MAAM,EAAE,eAAe,GAAG,IAAI,CAAA;IAC9B;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAA;IACjB,eAAe,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAA;IACtD,YAAY,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAA;IAC7C,SAAS,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,IAAI,CAAA;IACnD,MAAM,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,IAAI,CAAA;IAC1C;;;;;;;OAOG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,YAAY,GAAG,eAAe,KAAK,MAAM,GAAG,SAAS,CAAA;CACvE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/components/nav/types.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/components/status.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAM/C,eAAO,MAAM,gBAAgB,gDAAiD,aAAa;;;;CAe1F,CAAA"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/components/status.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAM/C,eAAO,MAAM,gBAAgB,GAAI,6CAA6C,aAAa;;;;CAe1F,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/helper/form.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,KAAK,CAAA;AAEpD,eAAO,MAAM,mBAAmB,WAAY,SAAS,KAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAGzE,CAAA"}
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/helper/form.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,KAAK,CAAA;AAEpD,eAAO,MAAM,mBAAmB,GAAI,QAAQ,SAAS,KAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAGzE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/client-panel",
3
- "version": "0.1.18-rc.2",
3
+ "version": "0.1.18-rc.21",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -36,18 +36,19 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@hookform/resolvers": "^5.2.2",
39
- "@owlmeans/client": "^0.1.18-rc.2",
40
- "@owlmeans/client-i18n": "^0.1.18-rc.2",
41
- "@owlmeans/client-entrypoint": "^0.1.18-rc.2",
42
- "@owlmeans/client-route": "^0.1.18-rc.2",
43
- "@owlmeans/error": "^0.1.18-rc.2",
44
- "@owlmeans/entrypoint": "^0.1.18-rc.2"
39
+ "@owlmeans/client": "^0.1.18-rc.14",
40
+ "@owlmeans/client-i18n": "^0.1.18-rc.13",
41
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.11",
42
+ "@owlmeans/client-route": "^0.1.18-rc.11",
43
+ "@owlmeans/error": "^0.1.18-rc.7",
44
+ "@owlmeans/entrypoint": "^0.1.18-rc.9"
45
45
  },
46
46
  "peerDependencies": {
47
- "@owlmeans/auth": "^0.1.18-rc.2",
48
- "@owlmeans/client-auth": "^0.1.18-rc.2",
49
- "@owlmeans/i18n": "^0.1.18-rc.2",
50
- "@owlmeans/router": "^0.1.18-rc.2",
47
+ "@owlmeans/auth": "^0.1.18-rc.8",
48
+ "@owlmeans/client-auth": "^0.1.18-rc.18",
49
+ "@owlmeans/config": "^0.1.18-rc.10",
50
+ "@owlmeans/i18n": "^0.1.18-rc.7",
51
+ "@owlmeans/router": "^0.1.18-rc.7",
51
52
  "ajv": "*",
52
53
  "react": "*",
53
54
  "react-hook-form": "*"
@@ -62,8 +63,8 @@
62
63
  },
63
64
  "devDependencies": {
64
65
  "@owlmeans/dep-config": "workspace:*",
65
- "@owlmeans/auth": "^0.1.18-rc.2",
66
- "@owlmeans/client-auth": "^0.1.18-rc.2",
66
+ "@owlmeans/auth": "^0.1.18-rc.8",
67
+ "@owlmeans/client-auth": "^0.1.18-rc.18",
67
68
  "@types/react": "^19.2.17",
68
69
  "nodemon": "^3.1.14",
69
70
  "react-hook-form": "^7.53.0",
package/src/auth/index.ts CHANGED
@@ -1,2 +1,2 @@
1
-
2
1
  export * from './i18n.js'
2
+ export * from './login/index.js'
@@ -0,0 +1,2 @@
1
+ export type * from './types.js'
2
+ export * from './model.js'
@@ -0,0 +1,101 @@
1
+ import { useCallback, useMemo, useState } from 'react'
2
+ import { useContext, useNavigate } from '@owlmeans/client'
3
+ import type { CommonConfig } from '@owlmeans/config'
4
+ import {
5
+ acceptTerms, primaryLoginMethod, resolveCredit, resolveTerms, termsAccepted,
6
+ LoginOutcome, LOGIN_SERVICE,
7
+ } from '@owlmeans/client-auth/login'
8
+ import type { LoginContext, LoginMethod, LoginService } from '@owlmeans/client-auth/login'
9
+ import type { LoginMethodsModel, UseLoginMethodsOptions } from './types.js'
10
+
11
+ /**
12
+ * Everything a sign-in screen needs, with no opinion about how it looks.
13
+ *
14
+ * Headless and cross-platform, like `usePanelNav`: the rules about what may be offered, what must
15
+ * be confirmed first and what the credit line says are the same on every platform, and only the
16
+ * rendering differs. A renderer that re-derives any of them will drift from the one that does not.
17
+ */
18
+ export const useLoginMethods = (opts?: UseLoginMethodsOptions): LoginMethodsModel => {
19
+ const context = useContext() as unknown as LoginContext
20
+ const nav = useNavigate()
21
+ const login = context.service<LoginService>(LOGIN_SERVICE)
22
+
23
+ const cfg = opts?.config ?? (context.cfg as CommonConfig).security?.auth?.login
24
+ const brand = (context.cfg as CommonConfig).brand
25
+
26
+ const resolved = useMemo(() => resolveTerms(opts?.terms ?? cfg?.terms), [opts?.terms, cfg])
27
+ const credit = useMemo(
28
+ () => resolveCredit(cfg?.credit, brand, context.cfg.service), [cfg, brand, context]
29
+ )
30
+
31
+ const [accepted, setAccepted] = useState(() => termsAccepted(resolved))
32
+ const [attempted, setAttempted] = useState(false)
33
+ const [busy, setBusy] = useState<string | null>(null)
34
+ const [outcome, setOutcome] = useState<LoginOutcome | null>(null)
35
+ const [error, setError] = useState<string | null>(null)
36
+
37
+ const env = login.env()
38
+ const all = login.methods({
39
+ context, env,
40
+ navigate: (alias, params) => { nav.go(alias, params != null ? { params } : undefined) },
41
+ })
42
+ const methods = typeof opts?.methods === 'function'
43
+ ? opts.methods(all)
44
+ : opts?.methods ?? all
45
+
46
+ const blocked = resolved != null && resolved.required && !accepted
47
+
48
+ const select = useCallback((method: LoginMethod) => {
49
+ if (blocked) {
50
+ // Nothing starts, and no window opens. The screen renders the reason instead — which is why
51
+ // a control must never be `disabled`: a disabled control swallows the click and says nothing.
52
+ setAttempted(true)
53
+
54
+ return
55
+ }
56
+ setBusy(method.id)
57
+ setError(null)
58
+ void method.start({
59
+ context, env,
60
+ navigate: (alias, params) => { nav.go(alias, params != null ? { params } : undefined) },
61
+ }).then(result => {
62
+ setOutcome(result)
63
+ // `Redirected` means the browser is leaving; keeping the control busy is honest there.
64
+ if (result !== LoginOutcome.Redirected) {
65
+ setBusy(null)
66
+ }
67
+ }).catch((e: Error) => {
68
+ setError(e.message)
69
+ setOutcome(LoginOutcome.Failed)
70
+ setBusy(null)
71
+ })
72
+ }, [blocked, context, env, nav])
73
+
74
+ const accept = useCallback((value: boolean) => {
75
+ setAccepted(value)
76
+ setAttempted(false)
77
+ acceptTerms(resolved, value)
78
+ }, [resolved])
79
+
80
+ return {
81
+ methods,
82
+ primary: primaryLoginMethod(methods),
83
+ terms: {
84
+ required: resolved?.required ?? false,
85
+ accepted: resolved == null ? true : accepted,
86
+ attempted,
87
+ urls: {
88
+ terms: resolved?.terms ?? '',
89
+ privacy: resolved?.privacy ?? '',
90
+ ...(resolved?.cookies != null ? { cookies: resolved.cookies } : {}),
91
+ },
92
+ accept,
93
+ },
94
+ credit,
95
+ blocked,
96
+ busy,
97
+ outcome,
98
+ error,
99
+ select,
100
+ }
101
+ }
@@ -0,0 +1,45 @@
1
+ import type { LoginMethod, LoginOutcome } from '@owlmeans/client-auth/login'
2
+ import type { LoginScreenConfig, LoginTermsConfig } from '@owlmeans/config'
3
+
4
+ export interface UseLoginMethodsOptions {
5
+ /** Overrides the configuration the context carries. */
6
+ config?: LoginScreenConfig
7
+ terms?: LoginTermsConfig | false
8
+ /** Replace or reorder what the resolver produced. */
9
+ methods?: LoginMethod[] | ((methods: LoginMethod[]) => LoginMethod[])
10
+ }
11
+
12
+ export interface LoginTermsModel {
13
+ required: boolean
14
+ accepted: boolean
15
+ /** A blocked selection was attempted — render the explanation. */
16
+ attempted: boolean
17
+ urls: { terms: string, privacy: string, cookies?: string }
18
+ accept: (value: boolean) => void
19
+ }
20
+
21
+ export interface LoginCreditModel {
22
+ poweredBy: boolean
23
+ line: string | null
24
+ }
25
+
26
+ export interface LoginMethodsModel {
27
+ methods: LoginMethod[]
28
+ /** The one a screen highlights and focuses. It is never started automatically. */
29
+ primary: LoginMethod | null
30
+ terms: LoginTermsModel
31
+ credit: LoginCreditModel
32
+ /** True while the terms have not been confirmed and confirming them is required. */
33
+ blocked: boolean
34
+ /** Id of the method currently starting. */
35
+ busy: string | null
36
+ outcome: LoginOutcome | null
37
+ error: string | null
38
+ /**
39
+ * Start a method.
40
+ *
41
+ * NOT async, and it awaits nothing before delegating — the flow may need to open a window, and a
42
+ * window opened after the click has finished being handled is eaten by the popup blocker.
43
+ */
44
+ select: (method: LoginMethod) => void
45
+ }
@@ -2,6 +2,7 @@ export type * from './types.js'
2
2
 
3
3
  export * from './form/index.js'
4
4
  export * from './layout/index.js'
5
+ export * from './nav/index.js'
5
6
  export * from './context.js'
6
7
  export * from './consts.js'
7
8
  export * from './status.js'
@@ -0,0 +1,136 @@
1
+ import { useMemo } from 'react'
2
+ import type { ClientRoute } from '@owlmeans/client-route'
3
+ import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
4
+ import type { Location } from '@owlmeans/router'
5
+ import { useContext, useNavigate } from '@owlmeans/client'
6
+
7
+ import type { NavTranslate, PanelNavConfig, PanelNavItem, PanelNavModel, PanelNavSection } from './types.js'
8
+
9
+ /** Default resolver: no i18n, the caller's fallback wins. See {@link NavTranslate}. */
10
+ export const defaultNavTranslate: NavTranslate = (_key, defaultValue) => defaultValue
11
+
12
+ /**
13
+ * Turn an alias into something readable — `my-app:web:user-list` becomes `User list`.
14
+ * The last segment is the meaningful one; the prefixes address the app, not the screen.
15
+ */
16
+ export const defaultNavLabel = (alias: string): string => {
17
+ const segment = alias.split(/[:.]/).filter(part => part !== '').pop() ?? alias
18
+ const words = segment.replace(/[-_]+/g, ' ').trim()
19
+
20
+ return words.charAt(0).toUpperCase() + words.slice(1)
21
+ }
22
+
23
+ export const resolveNavLabel = (
24
+ translate: NavTranslate, label: string | undefined, key: string, alias?: string
25
+ ): string => label ?? translate(key, defaultNavLabel(alias ?? key))
26
+
27
+ const visibleItems = (section: PanelNavSection): PanelNavItem[] =>
28
+ section.items.filter(item => item.hidden !== true)
29
+
30
+ const normalizePath = (path: string): string =>
31
+ path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path
32
+
33
+ /**
34
+ * The navigation model behind both menus.
35
+ *
36
+ * Resolving the CURRENT screen has two sources, and both are needed. The router carries the
37
+ * alias in `location.state`, which is authoritative — but `state` is `window.history.state`,
38
+ * so it is null until the first in-app navigation: on a hard page load or a deep link there
39
+ * is nothing there, and a menu keyed on it alone highlights nothing on exactly the entry that
40
+ * matters most. The pathname is the fallback: an entrypoint computes its path from its own
41
+ * declaration and its ancestors', so matching them is a lookup, not a guess.
42
+ */
43
+ export const usePanelNav = (config: PanelNavConfig): PanelNavModel => {
44
+ const context = useContext()
45
+ const nav = useNavigate()
46
+ const location: Location<ClientRoute> = context.router().useLocation()
47
+
48
+ const state = location.state as ClientRoute | null
49
+ const pathname = location.pathname
50
+
51
+ return useMemo(() => {
52
+ const sections = config.sections
53
+ .filter(section => section.hidden !== true)
54
+ .map(section => ({ ...section, items: visibleItems(section) }))
55
+
56
+ const pathOf = (alias: string): string | null => {
57
+ try {
58
+ return normalizePath(context.entrypoint<ClientEntrypoint<string>>(alias).path())
59
+ } catch {
60
+ // An alias the app never elevated addresses nothing — it cannot be the current screen,
61
+ // and it must not take the menu down with it.
62
+ return null
63
+ }
64
+ }
65
+
66
+ const all = sections.flatMap(section => section.items)
67
+
68
+ let current: string | null = state?.alias ?? null
69
+ if (current == null) {
70
+ const here = normalizePath(pathname)
71
+ const exact = all.find(item => pathOf(item.alias) === here)
72
+ if (exact != null) {
73
+ current = exact.alias
74
+ } else {
75
+ // Longest prefix: a detail screen under a listed one still belongs to its section.
76
+ const prefixed = all
77
+ .map(item => ({ item, path: pathOf(item.alias) }))
78
+ .filter((entry): entry is { item: PanelNavItem, path: string } =>
79
+ entry.path != null && entry.path !== '/' && here.startsWith(`${entry.path}/`))
80
+ .sort((a, b) => b.path.length - a.path.length)[0]
81
+ current = prefixed?.item.alias ?? null
82
+ }
83
+ }
84
+
85
+ const sectionOf = (alias: string | null): PanelNavSection | null =>
86
+ alias == null ? null : sections.find(section => section.items.some(item => item.alias === alias)) ?? null
87
+
88
+ let active = sectionOf(current)
89
+ if (active == null && current != null) {
90
+ // The current screen is not listed anywhere — walk up to the ancestor that is, so a
91
+ // nested screen keeps its section (and its side menu) rather than clearing the chrome.
92
+ let alias: string | null = current
93
+ const seen = new Set<string>()
94
+ while (alias != null && !seen.has(alias)) {
95
+ seen.add(alias)
96
+ try {
97
+ alias = context.entrypoint<ClientEntrypoint<string>>(alias).route.route.parent ?? null
98
+ } catch {
99
+ alias = null
100
+ }
101
+ const found = sectionOf(alias)
102
+ if (found != null) {
103
+ active = found
104
+ break
105
+ }
106
+ }
107
+ }
108
+
109
+ const isItemActive = (item: PanelNavItem): boolean => item.alias === current
110
+ const isSectionActive = (section: PanelNavSection): boolean =>
111
+ active != null && section.name === active.name
112
+
113
+ return {
114
+ sections,
115
+ current,
116
+ active,
117
+ showSide: active != null && active.items.length > 1,
118
+ isSectionActive,
119
+ isItemActive,
120
+ goSection: section => {
121
+ const first = section.items[0]
122
+ return first != null ? nav.press(first.alias) : () => {}
123
+ },
124
+ goItem: item => nav.press(item.alias),
125
+ hrefOf: target => {
126
+ const alias = 'alias' in target ? target.alias : target.items[0]?.alias
127
+ if (alias == null) {
128
+ return undefined
129
+ }
130
+ const path = pathOf(alias)
131
+
132
+ return path == null || path.includes(':') ? undefined : path
133
+ },
134
+ }
135
+ }, [config, pathname, state?.alias, context, nav])
136
+ }
@@ -0,0 +1,3 @@
1
+ export type * from './types.js'
2
+
3
+ export * from './helper.js'
@@ -0,0 +1,79 @@
1
+ import type { ComponentType } from 'react'
2
+
3
+ /**
4
+ * One screen in the navigation — the second menu level.
5
+ *
6
+ * `alias` addresses a frontend entrypoint, so an item never carries a URL: the router
7
+ * resolves it, and a path that changes shape stays correct everywhere it is rendered.
8
+ */
9
+ export interface PanelNavItem {
10
+ alias: string
11
+ /** Literal label. Absent, the label is resolved through `translate` and falls back to a humanized alias. */
12
+ label?: string
13
+ Icon?: ComponentType<{ className?: string }>
14
+ hidden?: boolean
15
+ }
16
+
17
+ /**
18
+ * One section — the first menu level. Its items are the screens the side menu offers
19
+ * while the section is active.
20
+ */
21
+ export interface PanelNavSection {
22
+ /** Stable key. Also the default translation key (`nav.<name>`). */
23
+ name: string
24
+ label?: string
25
+ /** Ordered. The first visible item is where pressing the section navigates. */
26
+ items: PanelNavItem[]
27
+ hidden?: boolean
28
+ }
29
+
30
+ export interface PanelNavConfig {
31
+ sections: PanelNavSection[]
32
+ }
33
+
34
+ /** A footer link: exactly one of `alias` (an entrypoint) or `href` (anything else). */
35
+ export interface PanelNavLink {
36
+ alias?: string
37
+ href?: string
38
+ label?: string
39
+ /** Open in a new tab. */
40
+ open?: boolean
41
+ }
42
+
43
+ /**
44
+ * Resolves a label from a translation key.
45
+ *
46
+ * It is a PROP, never an implicit context read: an app that mounts without an i18n provider
47
+ * (`renderApp` from `@owlmeans/web-client` does) must still render a menu, and reaching for
48
+ * the panel i18n context there throws inside the render tree and blanks the whole app.
49
+ * The default returns the fallback, so literal labels work with no i18n at all.
50
+ */
51
+ export interface NavTranslate {
52
+ (key: string, defaultValue: string): string
53
+ }
54
+
55
+ export interface PanelNavModel {
56
+ /** Sections and items with `hidden` filtered out. */
57
+ sections: PanelNavSection[]
58
+ /** Alias of the screen currently rendered, when it could be resolved. */
59
+ current: string | null
60
+ active: PanelNavSection | null
61
+ /**
62
+ * Whether the side menu carries anything worth showing — false when the active section
63
+ * holds a single screen, which is the whole point: one screen needs no second level.
64
+ */
65
+ showSide: boolean
66
+ isSectionActive: (section: PanelNavSection) => boolean
67
+ isItemActive: (item: PanelNavItem) => boolean
68
+ goSection: (section: PanelNavSection) => () => void
69
+ goItem: (item: PanelNavItem) => () => void
70
+ /**
71
+ * The resolved URL of a section's landing screen, when it has one.
72
+ *
73
+ * A menu entry navigates through `nav.press`, but it still has to BE a link: an `<a>`
74
+ * without `href` is not focusable, does not answer the keyboard, and cannot be opened in a
75
+ * new tab. Undefined for a path carrying route parameters — there is no honest URL to show
76
+ * for a screen whose address is not known yet.
77
+ */
78
+ hrefOf: (target: PanelNavItem | PanelNavSection) => string | undefined
79
+ }
package/build/.gitkeep DELETED
File without changes