@declarion/react 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,25 @@ export interface FilterNode {
11
11
  }
12
12
  /** All supported filter operator codes. */
13
13
  export type FilterOp = "eq" | "not_eq" | "gt" | "gte" | "lt" | "lte" | "in" | "not_in" | "between" | "contains" | "starts_with" | "is_empty" | "is_not_empty" | "today" | "this_week" | "this_month" | "last_7_days" | "last_30_days" | "last_hour" | "last_24_hours";
14
+ /**
15
+ * Resolution status of one filter value, produced by the filter-value label
16
+ * resolver (`useFilterValueLabels`). `special` / `resolved` carry a `label`
17
+ * and are terminal-synchronous (no flicker); `loading` is a ref fetch in
18
+ * flight (render a skeleton, never the raw uuid); `unresolved` is a ref that
19
+ * could not be labeled (deleted / no access); `plain` is a literal/number/date
20
+ * (rendered raw, as before).
21
+ */
22
+ export type FilterValueStatus = "special" | "resolved" | "loading" | "unresolved" | "plain";
23
+ /** Resolver result for one `(field, value)`: a label when known, the raw
24
+ * coerced value always (the terminal fallback). */
25
+ export interface FilterValueLabel {
26
+ status: FilterValueStatus;
27
+ label?: string;
28
+ raw: string;
29
+ }
30
+ /** Maps one stored filter `(field, value)` to its human label. The single
31
+ * source of chip + drawer value labels; see `useFilterValueLabels`. */
32
+ export type FilterValueResolver = (field: string, value: unknown) => FilterValueLabel;
14
33
  /** Operators that don't take a value. */
15
34
  export declare const NO_VALUE_OPS: Set<FilterOp>;
16
35
  /** Human-friendly display names for operators. */
@@ -30,8 +49,17 @@ export declare function hasOrGroups(nodes: FilterNode[]): boolean;
30
49
  export declare function flatFieldConditions(nodes: FilterNode[]): FilterNode[];
31
50
  /** Count active field conditions in the filter tree. */
32
51
  export declare function countActiveFilters(nodes: FilterNode[]): number;
33
- /** Format a filter node for chip display. */
34
- export declare function formatFilterChip(node: FilterNode): string;
52
+ /**
53
+ * Format a filter node for chip display as a string (op label + value).
54
+ *
55
+ * When a `resolveValue` resolver is passed, each value is humanized to its
56
+ * label for the synchronous statuses (`special` / `resolved`) and falls back
57
+ * to the raw value otherwise. Without a resolver, values render raw exactly as
58
+ * before. This string form is used in OR-group joins and as a fallback; the
59
+ * single-chip renderer (`FilterChips`) uses a richer per-value renderer that
60
+ * also shows a skeleton while a ref label loads.
61
+ */
62
+ export declare function formatFilterChip(node: FilterNode, resolveValue?: FilterValueResolver): string;
35
63
  /** Create a simple equality filter node. */
36
64
  export declare function eqFilter(field: string, value: string): FilterNode;
37
65
  /** Remove a filter node by field name, recursing into OR/AND groups. */
@@ -1,4 +1,4 @@
1
- import { type ReactElement } from "react";
1
+ import { type ReactElement, type ReactNode } from "react";
2
2
  import { type RenderResult } from "@testing-library/react";
3
3
  import { QueryClient } from "@tanstack/react-query";
4
4
  import { type ActionExecuteRequest, type ActionExecuteResponse } from "./api/data";
@@ -64,3 +64,21 @@ export declare function cleanupDeclarion(): void;
64
64
  * live queryClient.
65
65
  */
66
66
  export declare function renderWithDeclarion(ui: ReactElement, opts?: RenderWithDeclarionOptions): RenderWithDeclarionResult;
67
+ export interface PreviewHarnessProps extends RenderWithDeclarionOptions {
68
+ children: ReactNode;
69
+ /** Default true: seeds an admin user so useSchema's auth gate opens and the
70
+ * screen renders authenticated. Set false to render a pre-login screen
71
+ * (SignIn / SignUp / Forgot / Reset / TwoFA / AuthShell) in its logged-out
72
+ * state, where useSchema stays disabled (no fetch) as in production. */
73
+ authenticated?: boolean;
74
+ }
75
+ /**
76
+ * Component form of {@link renderWithDeclarion} for live previews / stories /
77
+ * Storybook: wraps `children` in the SDK's real providers (QueryClient +
78
+ * MemoryRouter + ActionRunnerProvider + the Sonner outlet) and seeds the same
79
+ * read/action seams from props, so an SDK screen renders against fixtures with
80
+ * no network. Unlike renderWithDeclarion (which calls testing-library's render),
81
+ * this returns an element you compose directly. Providers + seeds are created
82
+ * once per mount (useState initializer) so a static snapshot is deterministic.
83
+ */
84
+ export declare function PreviewHarness({ children, authenticated, ...opts }: PreviewHarnessProps): ReactElement;
@@ -1,11 +1,12 @@
1
- import { Pt as e, ct as t, lt as n, t as r, tt as i, ut as a } from "./ActionRunner-C2A73D9b.js";
2
- import { MemoryRouter as o } from "react-router-dom";
3
- import { QueryClient as s, QueryClientProvider as c } from "@tanstack/react-query";
4
- import { Toaster as l } from "sonner";
5
- import { jsx as u, jsxs as d } from "react/jsx-runtime";
6
- import { render as f } from "@testing-library/react";
1
+ import { It as e, dt as t, ft as n, rt as r, t as i, ut as a } from "./ActionRunner-eixtbT5K.js";
2
+ import { useState as o } from "react";
3
+ import { MemoryRouter as s } from "react-router-dom";
4
+ import { QueryClient as c, QueryClientProvider as l } from "@tanstack/react-query";
5
+ import { Toaster as u } from "sonner";
6
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
7
+ import { render as p } from "@testing-library/react";
7
8
  //#region src/testing.tsx
8
- function p(e = {}) {
9
+ function m(e = {}) {
9
10
  return {
10
11
  languages: [],
11
12
  structures: {},
@@ -18,7 +19,7 @@ function p(e = {}) {
18
19
  ...e
19
20
  };
20
21
  }
21
- function m(e, t = {}, n) {
22
+ function h(e, t = {}, n) {
22
23
  return {
23
24
  data: e,
24
25
  meta: {
@@ -32,27 +33,27 @@ function m(e, t = {}, n) {
32
33
  ...n === void 0 ? {} : { $refs: n }
33
34
  };
34
35
  }
35
- function h(e) {
36
+ function g(e) {
36
37
  return e;
37
38
  }
38
- var g = Symbol("declarion.testing.actionEnvelope");
39
- function _(e) {
39
+ var _ = Symbol("declarion.testing.actionEnvelope");
40
+ function v(e) {
40
41
  return {
41
42
  ...e,
42
- [g]: !0
43
+ [_]: !0
43
44
  };
44
45
  }
45
- function v(e) {
46
- if (e && typeof e == "object" && e[g]) {
46
+ function y(e) {
47
+ if (e && typeof e == "object" && e[_]) {
47
48
  let t = { ...e };
48
- return delete t[g], t;
49
+ return delete t[_], t;
49
50
  }
50
51
  return {
51
52
  status: "success",
52
53
  result: e
53
54
  };
54
55
  }
55
- function y() {
56
+ function b() {
56
57
  let t = {
57
58
  id: "test-tenant",
58
59
  code: "test",
@@ -71,7 +72,7 @@ function y() {
71
72
  };
72
73
  e.getState().setUser(n, t);
73
74
  }
74
- function b(e) {
75
+ function x(e) {
75
76
  if (e === void 0) return { rows: [] };
76
77
  if (Array.isArray(e)) return { rows: e };
77
78
  if (Array.isArray(e.data)) {
@@ -84,64 +85,85 @@ function b(e) {
84
85
  }
85
86
  return e;
86
87
  }
87
- function x(e) {
88
- let r = e.entityData ?? {};
89
- a((e, t) => {
90
- let n = r[e], i = b(n), a = i.meta?.per_page ?? 50, o = i.meta?.total ?? i.rows.length;
88
+ function S(e) {
89
+ let i = e.entityData ?? {};
90
+ n((e, t) => {
91
+ let n = i[e], r = x(n), a = r.meta?.per_page ?? 50, o = r.meta?.total ?? r.rows.length;
91
92
  return t.count === "only" ? {
92
93
  data: [],
93
94
  meta: {
94
95
  page: 1,
95
96
  per_page: a,
96
97
  total: o,
97
- total_pages: i.meta?.total_pages ?? Math.max(1, Math.ceil(o / a)),
98
- count_mode: i.meta?.count_mode ?? "exact"
98
+ total_pages: r.meta?.total_pages ?? Math.max(1, Math.ceil(o / a)),
99
+ count_mode: r.meta?.count_mode ?? "exact"
99
100
  }
100
101
  } : {
101
- data: i.rows,
102
+ data: r.rows,
102
103
  meta: {
103
- page: i.meta?.page ?? 1,
104
+ page: r.meta?.page ?? 1,
104
105
  per_page: a,
105
- has_more: i.meta?.has_more ?? !1
106
+ has_more: r.meta?.has_more ?? !1
106
107
  },
107
- ...i.refs === void 0 ? {} : { $refs: i.refs }
108
+ ...r.refs === void 0 ? {} : { $refs: r.refs }
108
109
  };
109
110
  });
110
111
  let o = e.entityItems ?? {};
111
- n((e, t) => {
112
- let n = typeof t == "string" ? t : i(t);
112
+ t((e, t) => {
113
+ let n = typeof t == "string" ? t : r(t);
113
114
  return { data: o[e]?.[n] };
114
115
  });
115
116
  let s = e.actionResults ?? {};
116
- t(async (e, t) => {
117
+ a(async (e, t) => {
117
118
  if (!(e in s)) throw Error(`renderWithDeclarion: no action result registered for "${e}" (seed it via actionResults)`);
118
119
  let n = s[e];
119
- return v(typeof n == "function" ? await n(t) : n);
120
+ return y(typeof n == "function" ? await n(t) : n);
120
121
  });
121
122
  }
122
- function S() {
123
- a(null), n(null), t(null), e.setState({
123
+ function C() {
124
+ n(null), t(null), a(null), e.setState({
124
125
  user: null,
125
126
  isAuthenticated: !1,
126
127
  activeTenant: null,
127
128
  tenants: []
128
129
  });
129
130
  }
130
- function C(e, t = {}) {
131
- let n = new s({ defaultOptions: { queries: {
131
+ function w(e, t = {}) {
132
+ let n = new c({ defaultOptions: { queries: {
132
133
  retry: !1,
133
134
  gcTime: Infinity
134
135
  } } });
135
- y(), n.setQueryData(["schema", !0], t.schema ?? p()), x(t);
136
- let i = f(/* @__PURE__ */ d(c, {
136
+ b(), n.setQueryData(["schema", !0], t.schema ?? m()), S(t);
137
+ let r = p(/* @__PURE__ */ f(l, {
137
138
  client: n,
138
- children: [/* @__PURE__ */ u(o, {
139
+ children: [/* @__PURE__ */ d(s, {
139
140
  initialEntries: [t.route ?? "/"],
140
- children: /* @__PURE__ */ u(r, { children: e })
141
- }), /* @__PURE__ */ u(l, {})]
141
+ children: /* @__PURE__ */ d(i, { children: e })
142
+ }), /* @__PURE__ */ d(u, {})]
142
143
  }));
143
- return Object.assign(i, { queryClient: n });
144
+ return Object.assign(r, { queryClient: n });
145
+ }
146
+ function T({ children: t, authenticated: n = !0, ...r }) {
147
+ let [a] = o(() => {
148
+ let t = new c({ defaultOptions: { queries: {
149
+ retry: !1,
150
+ gcTime: Infinity
151
+ } } });
152
+ return n ? (b(), t.setQueryData(["schema", !0], r.schema ?? m())) : e.setState({
153
+ user: null,
154
+ isAuthenticated: !1,
155
+ activeTenant: null,
156
+ tenants: []
157
+ }), S(r), t;
158
+ });
159
+ return /* @__PURE__ */ f(l, {
160
+ client: a,
161
+ children: [/* @__PURE__ */ d(s, {
162
+ initialEntries: [r.route ?? "/"],
163
+ children: /* @__PURE__ */ d(i, { children: t })
164
+ }), /* @__PURE__ */ d(u, {})]
165
+ });
144
166
  }
145
- typeof afterEach == "function" && afterEach(() => S());
167
+ typeof afterEach == "function" && afterEach(() => C());
146
168
  //#endregion
147
- export { _ as actionEnvelope, h as actionResults, S as cleanupDeclarion, m as entityDataFixture, C as renderWithDeclarion, p as schemaFixture };
169
+ export { T as PreviewHarness, v as actionEnvelope, g as actionResults, C as cleanupDeclarion, h as entityDataFixture, w as renderWithDeclarion, m as schemaFixture };
@@ -44,6 +44,29 @@ export interface RefConfig {
44
44
  display?: RefDisplay;
45
45
  fields?: string[];
46
46
  }
47
+ /** One declared filter-only option ({code, label}). `code` is stored into the
48
+ * filter when chosen (a literal or a `$user.*` token resolved server-side);
49
+ * `label` is the localized display string. Mirrors engine.FilterSpecialValue. */
50
+ export interface FilterSpecialValue {
51
+ code: string;
52
+ label: LocalizedString;
53
+ }
54
+ /** Per-screen override of a field's filter_special_values, keyed in
55
+ * Screen.filter_special_values by the filter field code. */
56
+ export interface ScreenFilterSpecialValues {
57
+ mode?: "append" | "replace" | "clear";
58
+ values?: FilterSpecialValue[];
59
+ }
60
+ /** Normalizes a handler-param ref (scalar entity-code string OR structured
61
+ * object) into a RefConfig. One helper shared by filters, SmartDetailPage
62
+ * fields, and ActionDialog params so the scalar shorthand and structured form
63
+ * reach the relation combobox identically. */
64
+ export declare function normalizeRefConfig(ref: string | RefConfig | undefined): RefConfig | undefined;
65
+ /** Computes the effective filter_special_values for a field on a screen by
66
+ * merging the field-level list with an optional screen override per its mode
67
+ * (append [default] / replace / clear). Pure; mirrors the engine's
68
+ * EffectiveFilterSpecialValues so the SDK and loader agree. */
69
+ export declare function effectiveFilterSpecialValues(fieldValues: FilterSpecialValue[] | undefined, override: ScreenFilterSpecialValues | undefined): FilterSpecialValue[];
47
70
  export interface UnitConfig {
48
71
  storage: string;
49
72
  display?: string;
@@ -98,6 +121,10 @@ export interface EntityField {
98
121
  enum?: string;
99
122
  files?: FileFieldDef;
100
123
  ref?: RefConfig;
124
+ /** Filter-only declared options (e.g. "Myself" -> $user.id) surfaced in this
125
+ * field's filter control. Inert everywhere else. A screen MAY override these
126
+ * per-screen via Screen.filter_special_values. */
127
+ filter_special_values?: FilterSpecialValue[];
101
128
  structure?: string;
102
129
  array?: boolean;
103
130
  copyable?: boolean;
@@ -529,6 +556,10 @@ export interface Screen {
529
556
  list_resizable?: boolean;
530
557
  detail_screen?: string;
531
558
  fixed_filters?: Record<string, unknown>;
559
+ /** Per-screen override of field-level filter_special_values, keyed by filter
560
+ * field code. Append/replace/clear the entity field's declared values for
561
+ * just this screen. Mirrors engine.Screen.FilterSpecialValues. */
562
+ filter_special_values?: Record<string, ScreenFilterSpecialValues>;
532
563
  views?: ViewDefinition[];
533
564
  /** Action slot promotion for list screens (rule-of-2). UI honors first 2.
534
565
  * Note: primary_record_actions (detail header) was removed in Task 5 of plan
@@ -639,7 +670,10 @@ export interface HandlerParam {
639
670
  description?: LocalizedString;
640
671
  color?: string;
641
672
  }>;
642
- ref?: string;
673
+ /** `type: ref` param target. Scalar entity-code string (shorthand) OR a
674
+ * structured RefConfig (non-id lookup / display). Normalize via
675
+ * normalizeRefConfig before use. */
676
+ ref?: string | RefConfig;
643
677
  display?: HandlerParamDisplay;
644
678
  /**
645
679
  * Narrows the param's option set. Values may be literals or templates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@declarion/react",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "React SDK for Declarion, the schema-driven business apps platform.",