@fragmentsx/runtime-react 0.1.0 → 0.2.0-dev-20260901074721

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,12 +8,11 @@ React.
8
8
  ## Что это решает
9
9
 
10
10
  Скомпилированный сервером фрагмент — это ESM-артефакт, который нужно
11
- загрузить, смонтировать в дерево React, подключить его стили и дать доступу
11
+ загрузить, смонтировать в дерево React, подключить его стили и дать доступ
12
12
  к метрикам и переменным области. `runtime-react` закрывает весь этот путь:
13
13
 
14
- - `FragmentsProvider` + `FragmentsClientProvider` — контекст клиента на
15
- дерево приложения;
16
- - `Area` / `FragmentRenderer` — резолвинг и рендер области/фрагмента;
14
+ - `FragmentsProvider` — контекст клиента на дерево приложения;
15
+ - `Area` / `FragmentRenderer` — резолвинг области и рендер фрагмента;
17
16
  - `useFragmentComponent`, `useFragmentStylesheet`, `FragmentStylesheet` —
18
17
  подгрузка компонента и его CSS;
19
18
  - `useFragmentMetrics`, `useArea`, `useProjectProperty` — доступ к метрикам,
@@ -25,27 +24,79 @@ React.
25
24
  pnpm add @fragmentsx/runtime-react @fragmentsx/fragments-client react react-dom
26
25
  ```
27
26
 
27
+ Пакет объявляет `react` в `peerDependencies` (`^17 || ^18 || ^19`).
28
+
29
+ Если приложение собирается через Vite — поставьте ещё плагин, который
30
+ резолвит bare imports внутри загружаемых артефактов:
31
+
32
+ ```bash
33
+ pnpm add -D @fragmentsx/vite-plugin
34
+ ```
35
+
28
36
  ## Использование
29
37
 
30
38
  ```tsx
31
- import { createBrowserClient, FragmentsProvider, Area } from '@fragmentsx/runtime-react';
39
+ import {
40
+ createBrowserClient,
41
+ FragmentsProvider,
42
+ Area,
43
+ } from '@fragmentsx/runtime-react';
32
44
 
33
45
  const client = createBrowserClient({
34
- apiUrl: 'https://api.fragments.example.com',
35
- projectId: 'my-project',
46
+ apiToken: import.meta.env.VITE_FRAGMENTS_API_TOKEN,
47
+ backendEndpoint: 'https://api.example.com',
36
48
  });
37
49
 
38
50
  function App() {
39
51
  return (
40
52
  <FragmentsProvider client={client}>
41
- <Area code="home" />
53
+ <Area areaCode="home" />
42
54
  </FragmentsProvider>
43
55
  );
44
56
  }
45
57
  ```
46
58
 
59
+ `apiToken` обязателен. `backendEndpoint` — источник манифеста области; если
60
+ не передан, клиент бьётся в `window.location.origin`, что удобно при
61
+ same-origin проксировании `/api` на публичный API площадки.
62
+
63
+ `Area` сама разрешает область и внутри себя обёрнута в `Suspense` — пока
64
+ манифест грузится, поддерево пустое. Под капотом:
65
+
66
+ ```
67
+ <Area areaCode> → GET <backendEndpoint>/api/area/<code>/compiled
68
+ → manifest.fragments[*].moduleUrl → import(артефакт)
69
+ → <FragmentStylesheet href={cssUrl}/> + рендер фрагмента
70
+ ```
71
+
72
+ ### Рендер конкретного фрагмента
73
+
74
+ Когда область разрешать не нужно, используйте `FragmentRenderer` напрямую:
75
+
76
+ ```tsx
77
+ import { FragmentRenderer } from '@fragmentsx/runtime-react';
78
+
79
+ <FragmentRenderer fragmentId={42} props={{ title: 'Привет' }} />
80
+ ```
81
+
82
+ `AreaProps` — `{ areaCode: string; options?: { timeout?, pause?, ssr? } }`.
83
+ `FragmentRendererProps` — `{ fragmentId: number; props?; globalCssUrl?;
84
+ projectProperties?; onError? }`.
85
+
86
+ ## Обработка ошибок
87
+
88
+ Пакет реэкспортирует типизированные ошибки транспорта, чтобы их можно было
89
+ различать в границах ошибок приложения:
90
+
91
+ ```ts
92
+ import {
93
+ FragmentsAreaError,
94
+ FragmentsApiVersionError,
95
+ FragmentsLoadError,
96
+ } from '@fragmentsx/runtime-react';
97
+ ```
98
+
47
99
  ## Пара с fragments-client
48
100
 
49
- `runtime-react` зависит от `@fragmentsx/fragments-client` как от транспортного
50
- ядра (`workspace:*` внутри монорепозитория, `^0.1.0` из npm) — устанавливать
51
- и обновлять пакеты стоит вместе.
101
+ `runtime-react` зависит от `@fragmentsx/fragments-client` как от
102
+ транспортного ядра — устанавливать и обновлять пакеты стоит вместе.
package/dist/index.d.ts CHANGED
@@ -20,13 +20,33 @@ import { ReactNode } from 'react';
20
20
  export declare const Area: FC<AreaProps>;
21
21
 
22
22
  /**
23
- * Props для Area компонента (D-09, сокращён Phase 73 B-13):
23
+ * Сентинел нерезолвленной области — возвращается вместо чтения `$area.read`,
24
+ * когда на сервере `options.ssr === false`, либо когда `options.pause === true`
25
+ * (suspense-режим).
26
+ *
27
+ * Совместим с `UseAreaResult` по форме (тип возврата хука не расширяется),
28
+ * поэтому правка остаётся minor: потребитель, передающий `rootFragmentId`
29
+ * в проп типа `number`, продолжает компилироваться. Заморожен — сравнение
30
+ * по ссылке в `Area.tsx` не должно ломаться мутацией снаружи.
31
+ */
32
+ export declare const AREA_UNRESOLVED: UseAreaResult;
33
+
34
+ /**
35
+ * Props для Area компонента (D-09, сокращён Phase 73 B-13; расширен quick 260830-rvp):
24
36
  * - areaCode — строковый код области (Area.code в backend schema)
25
37
  * - options.pause — пропустить рендер (conditional skip per legacy contract)
26
38
  * - options.timeout — network timeout для $area.resolve fetch
27
39
  * - options.ssr — отключить auto-resolve в SSR environment (default `true`);
28
40
  * в browser auto-resolve запускается всегда. Параллель к render-react
29
41
  * Area options.ssr.
42
+ * - options.props (INT-03) — объект мержится поверх серверных props (ключи
43
+ * потребителя выигрывают), функция вызывается с серверными props и её
44
+ * результат уходит в артефакт целиком. Обе формы поддержаны намеренно —
45
+ * паритет с legacy render-react, с которого мигрирует потребитель.
46
+ * - options.fallback (INT-04) — узел, видимый до разрешения области; default
47
+ * `null` (прежнее поведение).
48
+ * - options.onError (INT-04) — вызывается при броске внутри поддерева;
49
+ * default — только dev-лог границы (прежнее поведение).
30
50
  */
31
51
  export declare interface AreaProps {
32
52
  areaCode: string;
@@ -34,6 +54,9 @@ export declare interface AreaProps {
34
54
  timeout?: number;
35
55
  pause?: boolean;
36
56
  ssr?: boolean;
57
+ props?: Record<string, unknown> | ((serverProps: Record<string, unknown>) => Record<string, unknown>);
58
+ fallback?: ReactNode;
59
+ onError?: (error: Error) => void;
37
60
  };
38
61
  }
39
62
 
@@ -146,9 +169,10 @@ declare interface FragmentStylesheetProps {
146
169
  *
147
170
  * @param href - CDN URL стилевого файла
148
171
  * @param key - уникальный ключ для dedup (например `chunk:201` или `fragment:42:abc`)
149
- * @returns cleanup callback — при refCount = 0 удаляет `<link>` из head
172
+ * @returns handle — вызываемый cleanup (при refCount = 0 удаляет `<link>` из
173
+ * head) с дополнительным свойством `loaded: Promise<void>`
150
174
  */
151
- export declare const injectStylesheet: (href: string, key: string) => (() => void);
175
+ export declare const injectStylesheet: (href: string, key: string) => StylesheetHandle;
152
176
 
153
177
  export declare const InstanceIdContext: Context<string | null>;
154
178
 
@@ -203,7 +227,49 @@ export declare interface ReachGoalInfo {
203
227
  campaignId?: number;
204
228
  }
205
229
 
206
- export declare function useArea(areaCode: string, options?: UseAreaOptions): UseAreaResult;
230
+ /**
231
+ * Ref-counted `<link rel="stylesheet">` injector для compiled fragment CSS delivery (Req #11).
232
+ *
233
+ * **Single source of truth для ref-count dedup**: $cssChunks (client-core) делегирует
234
+ * DOM injection сюда через factory parameter (BLOCKER-4 architectural decision).
235
+ * Module-level registry гарантирует что за один bundle-scope один key = один <link>.
236
+ *
237
+ * Использование:
238
+ * ```ts
239
+ * const handle = injectStylesheet('https://cdn.example.com/chunk.css', 'chunk:201');
240
+ * await handle.loaded; // готовность CSS (load / error / таймаут — quick-260831-etq)
241
+ * // ... позже при unmount:
242
+ * handle();
243
+ * ```
244
+ *
245
+ * Ref-count semantics:
246
+ * - Первый вызов с key → создаёт `<link>`, refCount = 1
247
+ * - Повторный вызов с тем же key → refCount++, новый `<link>` НЕ создаётся
248
+ * - Cleanup → refCount--; при refCount = 0 → `<link>` удаляется из head
249
+ *
250
+ * Канал готовности (quick-260831-etq, D-5/D-6): возвращаемый handle несёт
251
+ * `loaded: Promise<void>`, который резолвится по `load`, по `error` (сбой
252
+ * трактуется как «готово» — показать без стилей лучше, чем зависнуть) или по
253
+ * истечении `STYLESHEET_READY_TIMEOUT_MS`, если ни одно событие не пришло
254
+ * (недоступный/молчащий CDN). Промис никогда не отвергается.
255
+ *
256
+ * SSR safety: в Node (isBrowser === false) возвращает свежий noop-handle с уже
257
+ * разрешённым `loaded` — общий экспорт `noop` не мутируется.
258
+ */
259
+ /** Канал готовности CSS у ref-counted injectStylesheet handle. */
260
+ declare type StylesheetHandle = (() => void) & {
261
+ loaded: Promise<void>;
262
+ };
263
+
264
+ /** Suspense-режим (по умолчанию либо явный `suspense: true`) — прежний throw-протокол. */
265
+ export declare function useArea(areaCode: string, options?: UseAreaOptions & {
266
+ suspense?: true;
267
+ }): UseAreaResult;
268
+
269
+ /** Не-suspense режим (INT-05) — явный `suspense: false` возвращает состояние вместо throw. */
270
+ export declare function useArea(areaCode: string, options: UseAreaOptions & {
271
+ suspense: false;
272
+ }): UseAreaState;
207
273
 
208
274
  /**
209
275
  * Опции хука. По дизайну API параллелен render-react useArea:
@@ -211,17 +277,31 @@ export declare function useArea(areaCode: string, options?: UseAreaOptions): Use
211
277
  * - `ssr` — стартовать ли auto-resolve на сервере (default `true`, чтобы
212
278
  * Next.js getServerSideProps мог hydration'ить с прогретым кэшем).
213
279
  * В browser environment auto-resolve всегда включён.
280
+ * - `pause` (INT-06) — пропустить резолв и чтение кэша вовсе; паритет
281
+ * с `AreaProps.options.pause`.
282
+ * - `suspense` (INT-05) — `false` переключает хук на `UseAreaState` вместо
283
+ * throw-протокола; по умолчанию `true` (прежнее поведение).
214
284
  */
215
- declare interface UseAreaOptions {
285
+ export declare interface UseAreaOptions {
216
286
  timeout?: number;
217
287
  ssr?: boolean;
288
+ pause?: boolean;
289
+ suspense?: boolean;
218
290
  }
219
291
 
220
292
  /**
221
293
  * Result shape возвращаемый useArea — зеркалирует AreaResolveResult из client-core.
222
294
  */
223
- declare interface UseAreaResult {
295
+ export declare interface UseAreaResult {
224
296
  rootFragmentId: number;
297
+ /**
298
+ * Quick 260831-dor (INT-10): id выбранного варианта — вход для атрибуции
299
+ * на стороне площадки-потребителя (префикс партнёрской ссылки трекера).
300
+ * `$area` уже нормализует отсутствие поля у backend'а в `null` (не `0` —
301
+ * `0` неотличим от настоящего id и молча испортил бы атрибуцию), поэтому
302
+ * поле обязательное и nullable, а не опциональное.
303
+ */
304
+ variantId: number | null;
225
305
  /**
226
306
  * Phase 73, B-02/B-06: значения props корневого артефакта, разрешённые сервером
227
307
  * (`resolveVariantProps`) — привязки `$ref` сюда не доезжают, только готовые значения.
@@ -244,6 +324,26 @@ declare interface UseAreaResult {
244
324
  globalCssUrl: string | null;
245
325
  }
246
326
 
327
+ /**
328
+ * Состояние, возвращаемое `useArea(code, {suspense:false})` вместо throw.
329
+ */
330
+ export declare interface UseAreaState {
331
+ status: UseAreaStatus;
332
+ data: UseAreaResult | null;
333
+ error: Error | null;
334
+ }
335
+
336
+ /**
337
+ * Union статуса не-suspense режима (quick 260830-rvp, INT-05):
338
+ * - `idle` — пауза (`options.pause`) либо серверный пропуск (`ssr:false` на сервере).
339
+ * - `loading` — кэш пуст, резолв запущен (идемпотентно), данных ещё нет.
340
+ * - `ready` — терминальный кэш-хит, данные доступны.
341
+ * - `not-found` — терминальная ошибка резолва (сохранённая в rejected-кэше);
342
+ * отдельного статуса под сетевой сбой нет намеренно — оба исхода для площадки
343
+ * означают «показать свой шаблон», различие доступно через поле `error`.
344
+ */
345
+ export declare type UseAreaStatus = "idle" | "loading" | "ready" | "not-found";
346
+
247
347
  /**
248
348
  * useCssChunk — no-op stub для compiled fragments.
249
349
  *
package/dist/index.es.js CHANGED
@@ -1,32 +1,32 @@
1
- import { jsx as o, jsxs as M, Fragment as j } from "react/jsx-runtime";
2
- import { createContext as f, useContext as a, useMemo as g, useLayoutEffect as N, useEffect as S, version as I, Component as R, Suspense as C } from "react";
3
- import { createFragmentsClient as k } from "@fragmentsx/fragments-client";
4
- import { FragmentsApiVersionError as ne, FragmentsAreaError as oe, FragmentsLoadError as se } from "@fragmentsx/fragments-client";
5
- const c = f(
1
+ import { jsx as l, jsxs as j, Fragment as I } from "react/jsx-runtime";
2
+ import { createContext as v, useContext as f, useMemo as P, useState as L, useEffect as F, useLayoutEffect as N, version as k, Component as b, Suspense as A } from "react";
3
+ import { AREA_GLOBAL_CSS_KEY as p, fragmentCssKey as O, fragmentFontsKey as U, createFragmentsClient as B } from "@fragmentsx/fragments-client";
4
+ import { FragmentsApiVersionError as oe, FragmentsAreaError as ie, FragmentsLoadError as fe } from "@fragmentsx/fragments-client";
5
+ const m = v(
6
6
  null
7
7
  );
8
- c.displayName = "FragmentsClientContext";
9
- function _(r) {
10
- return /* @__PURE__ */ o(c.Provider, { value: r.value, children: r.children });
8
+ m.displayName = "FragmentsClientContext";
9
+ function D(r) {
10
+ return /* @__PURE__ */ l(m.Provider, { value: r.value, children: r.children });
11
11
  }
12
- function b() {
13
- return a(c);
12
+ function K() {
13
+ return f(m);
14
14
  }
15
- const v = f(null);
16
- v.displayName = "InstanceIdContext";
17
- const h = f(
15
+ const S = v(null);
16
+ S.displayName = "InstanceIdContext";
17
+ const g = v(
18
18
  null
19
19
  );
20
- h.displayName = "ModulesMapContext";
21
- function B({
20
+ g.displayName = "ModulesMapContext";
21
+ function V({
22
22
  value: r,
23
23
  children: e
24
24
  }) {
25
- return /* @__PURE__ */ o(h.Provider, { value: r, children: e });
25
+ return /* @__PURE__ */ l(g.Provider, { value: r, children: e });
26
26
  }
27
- function H() {
28
- const r = a(c);
29
- return g(
27
+ function re() {
28
+ const r = f(m);
29
+ return P(
30
30
  () => ({
31
31
  reachGoal: (e) => {
32
32
  var n, t;
@@ -36,48 +36,90 @@ function H() {
36
36
  [r]
37
37
  );
38
38
  }
39
- function L(r) {
40
- const e = a(h);
39
+ function G(r) {
40
+ const e = f(g);
41
41
  if (!e)
42
42
  throw new Error("[useFragmentComponent] No ModulesMapProvider in tree");
43
43
  return e.read(r);
44
44
  }
45
- const U = typeof window < "u";
45
+ const Y = typeof window < "u", x = Object.freeze({
46
+ rootFragmentId: 0,
47
+ variantId: null,
48
+ props: {},
49
+ projectProperties: {},
50
+ globalCssUrl: null
51
+ });
46
52
  function $(r, e) {
47
- const n = a(c);
53
+ const n = f(m);
48
54
  if (!n)
49
55
  throw new Error("[useArea] No FragmentsClientProvider in tree");
50
56
  const t = n.$area;
51
57
  if (!(t != null && t.read))
52
58
  throw new Error("[useArea] $area plugin not mounted");
53
- if ((U || ((e == null ? void 0 : e.ssr) ?? !0)) && !t.has(r)) {
54
- const i = (e == null ? void 0 : e.timeout) != null ? { timeout: e.timeout } : void 0;
55
- t.resolve(r, i);
56
- }
57
- return t.read(r);
59
+ const s = (e == null ? void 0 : e.suspense) ?? !0, d = (e == null ? void 0 : e.pause) === !0 || !Y && (e == null ? void 0 : e.ssr) === !1, a = (e == null ? void 0 : e.timeout) != null ? { timeout: e.timeout } : void 0, [, u] = L(0);
60
+ if (F(() => {
61
+ if (s || d) return;
62
+ let c = !0;
63
+ return Promise.resolve(t.resolve(r, a)).catch(() => {
64
+ }).finally(() => {
65
+ c && u((o) => o + 1);
66
+ }), () => {
67
+ c = !1;
68
+ };
69
+ }, [r, s, e == null ? void 0 : e.pause, e == null ? void 0 : e.timeout, e == null ? void 0 : e.ssr]), s)
70
+ return d ? x : (t.has(r) || Promise.resolve(t.resolve(r, a)).catch(() => {
71
+ }), t.read(r));
72
+ if (d)
73
+ return { status: "idle", data: null, error: null };
74
+ if (t.has(r))
75
+ try {
76
+ return { status: "ready", data: t.read(r), error: null };
77
+ } catch (c) {
78
+ return {
79
+ status: "not-found",
80
+ data: null,
81
+ error: c instanceof Error ? c : new Error(String(c))
82
+ };
83
+ }
84
+ return Promise.resolve(t.resolve(r, a)).catch(() => {
85
+ }), { status: "loading", data: null, error: null };
58
86
  }
59
- const E = typeof window < "u", D = () => {
60
- }, d = /* @__PURE__ */ new Map(), F = (r, e) => {
61
- if (!E)
62
- return D;
63
- const n = d.get(e);
64
- if (n)
65
- return n.refCount += 1, () => {
66
- n.refCount -= 1, n.refCount === 0 && (n.linkEl.remove(), d.delete(e));
87
+ const w = typeof window < "u", z = 3e3, h = /* @__PURE__ */ new Map(), M = (r, e) => {
88
+ if (!w) {
89
+ const i = () => {
67
90
  };
91
+ return i.loaded = Promise.resolve(), i;
92
+ }
93
+ const n = h.get(e);
94
+ if (n) {
95
+ n.refCount += 1;
96
+ const i = () => {
97
+ n.refCount -= 1, n.refCount === 0 && (n.clearReadyTimer(), n.linkEl.remove(), h.delete(e));
98
+ };
99
+ return i.loaded = n.loaded, i;
100
+ }
68
101
  const t = document.createElement("link");
69
- t.rel = "stylesheet", t.href = r, t.setAttribute("data-fragment-key", e), document.head.appendChild(t);
70
- const s = { refCount: 1, linkEl: t };
71
- return d.set(e, s), () => {
72
- s.refCount -= 1, s.refCount === 0 && (s.linkEl.remove(), d.delete(e));
102
+ t.rel = "stylesheet", t.href = r, t.setAttribute("data-fragment-key", e);
103
+ let s;
104
+ const d = new Promise((i) => {
105
+ s = i;
106
+ }), a = () => {
107
+ c(), s();
108
+ }, u = setTimeout(a, z), c = () => clearTimeout(u);
109
+ t.addEventListener("load", a), t.addEventListener("error", a), document.head.appendChild(t);
110
+ const o = { refCount: 1, linkEl: t, loaded: d, clearReadyTimer: c };
111
+ h.set(e, o);
112
+ const y = () => {
113
+ o.refCount -= 1, o.refCount === 0 && (o.clearReadyTimer(), o.linkEl.remove(), h.delete(e));
73
114
  };
74
- }, O = E ? N : S;
75
- function V(r, e) {
76
- O(() => F(r, e), [r, e]);
115
+ return y.loaded = d, y;
116
+ }, H = w ? N : F;
117
+ function J(r, e) {
118
+ H(() => M(r, e), [r, e]);
77
119
  }
78
- const G = parseInt(I.split(".")[0], 10), K = G >= 19;
79
- function m({ href: r, fragmentKey: e }) {
80
- return K ? /* @__PURE__ */ o(
120
+ const q = parseInt(k.split(".")[0], 10), Q = q >= 19;
121
+ function E({ href: r, fragmentKey: e }) {
122
+ return Q ? /* @__PURE__ */ l(
81
123
  "link",
82
124
  {
83
125
  rel: "stylesheet",
@@ -85,13 +127,13 @@ function m({ href: r, fragmentKey: e }) {
85
127
  precedence: "default",
86
128
  "data-fragment-key": e
87
129
  }
88
- ) : (V(r, e), null);
130
+ ) : (J(r, e), null);
89
131
  }
90
- function Q(r) {
132
+ function te(r) {
91
133
  }
92
- const P = {}, p = f(null);
93
- p.displayName = "ProjectPropertiesContext";
94
- class y extends R {
134
+ const R = {}, C = v(null);
135
+ C.displayName = "ProjectPropertiesContext";
136
+ class T extends b {
95
137
  constructor() {
96
138
  super(...arguments), this.state = { hasError: !1 };
97
139
  }
@@ -106,97 +148,101 @@ class y extends R {
106
148
  return this.state.hasError ? null : this.props.children;
107
149
  }
108
150
  }
109
- const x = ({
151
+ const _ = ({
110
152
  fragmentId: r,
111
153
  props: e,
112
154
  globalCssUrl: n = null,
113
- projectProperties: t = P
155
+ projectProperties: t = R
114
156
  }) => {
115
- const s = L(r), l = b().$fragments, u = l == null ? void 0 : l.getRef(r), w = l == null ? void 0 : l.getScopeClass(r), A = e ?? {};
116
- return /* @__PURE__ */ M(j, { children: [
117
- n != null && /* @__PURE__ */ o(
118
- m,
157
+ const s = G(r), a = K().$fragments, u = a == null ? void 0 : a.getRef(r), c = a == null ? void 0 : a.getScopeClass(r), o = e ?? {};
158
+ return /* @__PURE__ */ j(I, { children: [
159
+ n != null && /* @__PURE__ */ l(
160
+ E,
119
161
  {
120
162
  href: n,
121
- fragmentKey: "global"
163
+ fragmentKey: p
122
164
  }
123
165
  ),
124
- u && /* @__PURE__ */ o(
125
- m,
166
+ u && /* @__PURE__ */ l(
167
+ E,
126
168
  {
127
169
  href: u.cssUrl,
128
- fragmentKey: `fragment:${r}:css`
170
+ fragmentKey: O(r)
129
171
  }
130
172
  ),
131
- (u == null ? void 0 : u.fontsUrl) != null && /* @__PURE__ */ o(
132
- m,
173
+ (u == null ? void 0 : u.fontsUrl) != null && /* @__PURE__ */ l(
174
+ E,
133
175
  {
134
176
  href: u.fontsUrl,
135
- fragmentKey: `fragment:${r}:fonts`
177
+ fragmentKey: U(r)
136
178
  }
137
179
  ),
138
- /* @__PURE__ */ o(p.Provider, { value: t, children: /* @__PURE__ */ o(v.Provider, { value: null, children: /* @__PURE__ */ o(
180
+ /* @__PURE__ */ l(C.Provider, { value: t, children: /* @__PURE__ */ l(S.Provider, { value: null, children: /* @__PURE__ */ l(
139
181
  "div",
140
182
  {
141
- className: w,
183
+ className: c,
142
184
  style: { display: "contents" },
143
- children: /* @__PURE__ */ o(s, { ...A })
185
+ children: /* @__PURE__ */ l(s, { ...o })
144
186
  }
145
187
  ) }) })
146
188
  ] });
147
- }, W = ({ onError: r, ...e }) => /* @__PURE__ */ o(C, { fallback: null, children: /* @__PURE__ */ o(y, { onError: r, children: /* @__PURE__ */ o(x, { ...e }) }) }), T = ({ areaCode: r, options: e }) => {
189
+ }, ne = ({ onError: r, ...e }) => /* @__PURE__ */ l(A, { fallback: null, children: /* @__PURE__ */ l(T, { onError: r, children: /* @__PURE__ */ l(_, { ...e }) }) }), W = ({ areaCode: r, options: e }) => {
148
190
  if ((e == null ? void 0 : e.pause) === !0)
149
191
  return null;
150
- const { rootFragmentId: n, props: t, projectProperties: s, globalCssUrl: i } = $(r, {
192
+ const n = $(r, {
151
193
  timeout: e == null ? void 0 : e.timeout,
152
194
  ssr: e == null ? void 0 : e.ssr
153
195
  });
154
- return /* @__PURE__ */ o(
155
- x,
196
+ if (n === x)
197
+ return null;
198
+ const { rootFragmentId: t, props: s, projectProperties: d, globalCssUrl: a } = n, u = typeof (e == null ? void 0 : e.props) == "function" ? e.props(s) : e != null && e.props ? { ...s, ...e.props } : s;
199
+ return /* @__PURE__ */ l(
200
+ _,
156
201
  {
157
- fragmentId: n,
158
- props: t,
159
- globalCssUrl: i,
160
- projectProperties: s
202
+ fragmentId: t,
203
+ props: u,
204
+ globalCssUrl: a,
205
+ projectProperties: d
161
206
  }
162
207
  );
163
- }, X = ({ areaCode: r, options: e }) => /* @__PURE__ */ o(C, { fallback: null, children: /* @__PURE__ */ o(y, { children: /* @__PURE__ */ o(T, { areaCode: r, options: e }) }) });
164
- function Y({ client: r, children: e }) {
165
- const n = g(
208
+ }, le = ({ areaCode: r, options: e }) => /* @__PURE__ */ l(A, { fallback: (e == null ? void 0 : e.fallback) ?? null, children: /* @__PURE__ */ l(T, { onError: e == null ? void 0 : e.onError, children: /* @__PURE__ */ l(W, { areaCode: r, options: e }) }) });
209
+ function ae({ client: r, children: e }) {
210
+ const n = P(
166
211
  () => ({
167
212
  read: (t) => r.$fragments.read(t)
168
213
  }),
169
214
  [r]
170
215
  );
171
- return /* @__PURE__ */ o(_, { value: r, children: /* @__PURE__ */ o(B, { value: n, children: e }) });
216
+ return /* @__PURE__ */ l(D, { value: r, children: /* @__PURE__ */ l(V, { value: n, children: e }) });
172
217
  }
173
- function Z(r) {
174
- return (a(p) ?? P)[r];
218
+ function se(r) {
219
+ return (f(C) ?? R)[r];
175
220
  }
176
- function ee(r) {
177
- return k({ ...r, inject: F });
221
+ function ue(r) {
222
+ return B({ ...r, inject: M });
178
223
  }
179
224
  export {
180
- X as Area,
181
- W as FragmentRenderer,
182
- m as FragmentStylesheet,
183
- ne as FragmentsApiVersionError,
184
- oe as FragmentsAreaError,
185
- c as FragmentsClientContext,
186
- _ as FragmentsClientProvider,
187
- se as FragmentsLoadError,
188
- Y as FragmentsProvider,
189
- v as InstanceIdContext,
190
- h as ModulesMapContext,
191
- B as ModulesMapProvider,
192
- p as ProjectPropertiesContext,
193
- ee as createBrowserClient,
194
- P as defaultProjectProperties,
195
- F as injectStylesheet,
225
+ x as AREA_UNRESOLVED,
226
+ le as Area,
227
+ ne as FragmentRenderer,
228
+ E as FragmentStylesheet,
229
+ oe as FragmentsApiVersionError,
230
+ ie as FragmentsAreaError,
231
+ m as FragmentsClientContext,
232
+ D as FragmentsClientProvider,
233
+ fe as FragmentsLoadError,
234
+ ae as FragmentsProvider,
235
+ S as InstanceIdContext,
236
+ g as ModulesMapContext,
237
+ V as ModulesMapProvider,
238
+ C as ProjectPropertiesContext,
239
+ ue as createBrowserClient,
240
+ R as defaultProjectProperties,
241
+ M as injectStylesheet,
196
242
  $ as useArea,
197
- Q as useCssChunk,
198
- L as useFragmentComponent,
199
- H as useFragmentMetrics,
200
- V as useFragmentStylesheet,
201
- Z as useProjectProperty
243
+ te as useCssChunk,
244
+ G as useFragmentComponent,
245
+ re as useFragmentMetrics,
246
+ J as useFragmentStylesheet,
247
+ se as useProjectProperty
202
248
  };
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- (function(t,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("react/jsx-runtime"),require("react"),require("@fragmentsx/fragments-client")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","@fragmentsx/fragments-client"],n):(t=typeof globalThis<"u"?globalThis:t||self,n(t.FragmentsRuntimeReact={},t.jsxRuntime,t.React,t.fragmentsClient))})(this,function(t,n,u,d){"use strict";const a=u.createContext(null);a.displayName="FragmentsClientContext";function y(r){return n.jsx(a.Provider,{value:r.value,children:r.children})}function b(){return u.useContext(a)}const F=u.createContext(null);F.displayName="InstanceIdContext";const f=u.createContext(null);f.displayName="ModulesMapContext";function E({value:r,children:e}){return n.jsx(f.Provider,{value:r,children:e})}function x(){const r=u.useContext(a);return u.useMemo(()=>({reachGoal:e=>{var o,s;(s=(o=r==null?void 0:r.$metrics)==null?void 0:o.reachGoal)==null||s.call(o,e)}}),[r])}function p(r){const e=u.useContext(f);if(!e)throw new Error("[useFragmentComponent] No ModulesMapProvider in tree");return e.read(r)}const I=typeof window<"u";function M(r,e){const o=u.useContext(a);if(!o)throw new Error("[useArea] No FragmentsClientProvider in tree");const s=o.$area;if(!(s!=null&&s.read))throw new Error("[useArea] $area plugin not mounted");if((I||((e==null?void 0:e.ssr)??!0))&&!s.has(r)){const h=(e==null?void 0:e.timeout)!=null?{timeout:e.timeout}:void 0;s.resolve(r,h)}return s.read(r)}const j=typeof window<"u",N=()=>{},m=new Map,P=(r,e)=>{if(!j)return N;const o=m.get(e);if(o)return o.refCount+=1,()=>{o.refCount-=1,o.refCount===0&&(o.linkEl.remove(),m.delete(e))};const s=document.createElement("link");s.rel="stylesheet",s.href=r,s.setAttribute("data-fragment-key",e),document.head.appendChild(s);const l={refCount:1,linkEl:s};return m.set(e,l),()=>{l.refCount-=1,l.refCount===0&&(l.linkEl.remove(),m.delete(e))}},k=j?u.useLayoutEffect:u.useEffect;function A(r,e){k(()=>P(r,e),[r,e])}const O=parseInt(u.version.split(".")[0],10)>=19;function g({href:r,fragmentKey:e}){return O?n.jsx("link",{rel:"stylesheet",href:r,precedence:"default","data-fragment-key":e}):(A(r,e),null)}function _(r){}const v={},C=u.createContext(null);C.displayName="ProjectPropertiesContext";class w extends u.Component{constructor(){super(...arguments),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e,o){var s,l;process.env.NODE_ENV!=="production"&&console.error("[fragments] Area error:",e,o.componentStack),(l=(s=this.props).onError)==null||l.call(s,e)}render(){return this.state.hasError?null:this.props.children}}const S=({fragmentId:r,props:e,globalCssUrl:o=null,projectProperties:s=v})=>{const l=p(r),c=b().$fragments,i=c==null?void 0:c.getRef(r),q=c==null?void 0:c.getScopeClass(r),D=e??{};return n.jsxs(n.Fragment,{children:[o!=null&&n.jsx(g,{href:o,fragmentKey:"global"}),i&&n.jsx(g,{href:i.cssUrl,fragmentKey:`fragment:${r}:css`}),(i==null?void 0:i.fontsUrl)!=null&&n.jsx(g,{href:i.fontsUrl,fragmentKey:`fragment:${r}:fonts`}),n.jsx(C.Provider,{value:s,children:n.jsx(F.Provider,{value:null,children:n.jsx("div",{className:q,style:{display:"contents"},children:n.jsx(l,{...D})})})})]})},T=({onError:r,...e})=>n.jsx(u.Suspense,{fallback:null,children:n.jsx(w,{onError:r,children:n.jsx(S,{...e})})}),B=({areaCode:r,options:e})=>{if((e==null?void 0:e.pause)===!0)return null;const{rootFragmentId:o,props:s,projectProperties:l,globalCssUrl:h}=M(r,{timeout:e==null?void 0:e.timeout,ssr:e==null?void 0:e.ssr});return n.jsx(S,{fragmentId:o,props:s,globalCssUrl:h,projectProperties:l})},L=({areaCode:r,options:e})=>n.jsx(u.Suspense,{fallback:null,children:n.jsx(w,{children:n.jsx(B,{areaCode:r,options:e})})});function U({client:r,children:e}){const o=u.useMemo(()=>({read:s=>r.$fragments.read(s)}),[r]);return n.jsx(y,{value:r,children:n.jsx(E,{value:o,children:e})})}function V(r){return(u.useContext(C)??v)[r]}function $(r){return d.createFragmentsClient({...r,inject:P})}Object.defineProperty(t,"FragmentsApiVersionError",{enumerable:!0,get:()=>d.FragmentsApiVersionError}),Object.defineProperty(t,"FragmentsAreaError",{enumerable:!0,get:()=>d.FragmentsAreaError}),Object.defineProperty(t,"FragmentsLoadError",{enumerable:!0,get:()=>d.FragmentsLoadError}),t.Area=L,t.FragmentRenderer=T,t.FragmentStylesheet=g,t.FragmentsClientContext=a,t.FragmentsClientProvider=y,t.FragmentsProvider=U,t.InstanceIdContext=F,t.ModulesMapContext=f,t.ModulesMapProvider=E,t.ProjectPropertiesContext=C,t.createBrowserClient=$,t.defaultProjectProperties=v,t.injectStylesheet=P,t.useArea=M,t.useCssChunk=_,t.useFragmentComponent=p,t.useFragmentMetrics=x,t.useFragmentStylesheet=A,t.useProjectProperty=V,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
1
+ (function(t,s){typeof exports=="object"&&typeof module<"u"?s(exports,require("react/jsx-runtime"),require("react"),require("@fragmentsx/fragments-client")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","@fragmentsx/fragments-client"],s):(t=typeof globalThis<"u"?globalThis:t||self,s(t.FragmentsRuntimeReact={},t.jsxRuntime,t.React,t.fragmentsClient))})(this,function(t,s,u,m){"use strict";const g=u.createContext(null);g.displayName="FragmentsClientContext";function M(r){return s.jsx(g.Provider,{value:r.value,children:r.children})}function N(){return u.useContext(g)}const y=u.createContext(null);y.displayName="InstanceIdContext";const E=u.createContext(null);E.displayName="ModulesMapContext";function j({value:r,children:e}){return s.jsx(E.Provider,{value:r,children:e})}function k(){const r=u.useContext(g);return u.useMemo(()=>({reachGoal:e=>{var l,n;(n=(l=r==null?void 0:r.$metrics)==null?void 0:l.reachGoal)==null||n.call(l,e)}}),[r])}function w(r){const e=u.useContext(E);if(!e)throw new Error("[useFragmentComponent] No ModulesMapProvider in tree");return e.read(r)}const x=typeof window<"u",F=Object.freeze({rootFragmentId:0,variantId:null,props:{},projectProperties:{},globalCssUrl:null});function T(r,e){const l=u.useContext(g);if(!l)throw new Error("[useArea] No FragmentsClientProvider in tree");const n=l.$area;if(!(n!=null&&n.read))throw new Error("[useArea] $area plugin not mounted");const c=(e==null?void 0:e.suspense)??!0,i=(e==null?void 0:e.pause)===!0||!x&&(e==null?void 0:e.ssr)===!1,a=(e==null?void 0:e.timeout)!=null?{timeout:e.timeout}:void 0,[,d]=u.useState(0);if(u.useEffect(()=>{if(c||i)return;let o=!0;return Promise.resolve(n.resolve(r,a)).catch(()=>{}).finally(()=>{o&&d(f=>f+1)}),()=>{o=!1}},[r,c,e==null?void 0:e.pause,e==null?void 0:e.timeout,e==null?void 0:e.ssr]),c)return i?F:(n.has(r)||Promise.resolve(n.resolve(r,a)).catch(()=>{}),n.read(r));if(i)return{status:"idle",data:null,error:null};if(n.has(r))try{return{status:"ready",data:n.read(r),error:null}}catch(o){return{status:"not-found",data:null,error:o instanceof Error?o:new Error(String(o))}}return Promise.resolve(n.resolve(r,a)).catch(()=>{}),{status:"loading",data:null,error:null}}const _=typeof window<"u",U=3e3,v=new Map,A=(r,e)=>{if(!_){const h=()=>{};return h.loaded=Promise.resolve(),h}const l=v.get(e);if(l){l.refCount+=1;const h=()=>{l.refCount-=1,l.refCount===0&&(l.clearReadyTimer(),l.linkEl.remove(),v.delete(e))};return h.loaded=l.loaded,h}const n=document.createElement("link");n.rel="stylesheet",n.href=r,n.setAttribute("data-fragment-key",e);let c;const i=new Promise(h=>{c=h}),a=()=>{o(),c()},d=setTimeout(a,U),o=()=>clearTimeout(d);n.addEventListener("load",a),n.addEventListener("error",a),document.head.appendChild(n);const f={refCount:1,linkEl:n,loaded:i,clearReadyTimer:o};v.set(e,f);const O=()=>{f.refCount-=1,f.refCount===0&&(f.clearReadyTimer(),f.linkEl.remove(),v.delete(e))};return O.loaded=i,O},B=_?u.useLayoutEffect:u.useEffect;function b(r,e){B(()=>A(r,e),[r,e])}const D=parseInt(u.version.split(".")[0],10)>=19;function C({href:r,fragmentKey:e}){return D?s.jsx("link",{rel:"stylesheet",href:r,precedence:"default","data-fragment-key":e}):(b(r,e),null)}function V(r){}const S={},P=u.createContext(null);P.displayName="ProjectPropertiesContext";class I extends u.Component{constructor(){super(...arguments),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e,l){var n,c;process.env.NODE_ENV!=="production"&&console.error("[fragments] Area error:",e,l.componentStack),(c=(n=this.props).onError)==null||c.call(n,e)}render(){return this.state.hasError?null:this.props.children}}const L=({fragmentId:r,props:e,globalCssUrl:l=null,projectProperties:n=S})=>{const c=w(r),a=N().$fragments,d=a==null?void 0:a.getRef(r),o=a==null?void 0:a.getScopeClass(r),f=e??{};return s.jsxs(s.Fragment,{children:[l!=null&&s.jsx(C,{href:l,fragmentKey:m.AREA_GLOBAL_CSS_KEY}),d&&s.jsx(C,{href:d.cssUrl,fragmentKey:m.fragmentCssKey(r)}),(d==null?void 0:d.fontsUrl)!=null&&s.jsx(C,{href:d.fontsUrl,fragmentKey:m.fragmentFontsKey(r)}),s.jsx(P.Provider,{value:n,children:s.jsx(y.Provider,{value:null,children:s.jsx("div",{className:o,style:{display:"contents"},children:s.jsx(c,{...f})})})})]})},K=({onError:r,...e})=>s.jsx(u.Suspense,{fallback:null,children:s.jsx(I,{onError:r,children:s.jsx(L,{...e})})}),q=({areaCode:r,options:e})=>{if((e==null?void 0:e.pause)===!0)return null;const l=T(r,{timeout:e==null?void 0:e.timeout,ssr:e==null?void 0:e.ssr});if(l===F)return null;const{rootFragmentId:n,props:c,projectProperties:i,globalCssUrl:a}=l,d=typeof(e==null?void 0:e.props)=="function"?e.props(c):e!=null&&e.props?{...c,...e.props}:c;return s.jsx(L,{fragmentId:n,props:d,globalCssUrl:a,projectProperties:i})},G=({areaCode:r,options:e})=>s.jsx(u.Suspense,{fallback:(e==null?void 0:e.fallback)??null,children:s.jsx(I,{onError:e==null?void 0:e.onError,children:s.jsx(q,{areaCode:r,options:e})})});function R({client:r,children:e}){const l=u.useMemo(()=>({read:n=>r.$fragments.read(n)}),[r]);return s.jsx(M,{value:r,children:s.jsx(j,{value:l,children:e})})}function Y(r){return(u.useContext(P)??S)[r]}function J(r){return m.createFragmentsClient({...r,inject:A})}Object.defineProperty(t,"FragmentsApiVersionError",{enumerable:!0,get:()=>m.FragmentsApiVersionError}),Object.defineProperty(t,"FragmentsAreaError",{enumerable:!0,get:()=>m.FragmentsAreaError}),Object.defineProperty(t,"FragmentsLoadError",{enumerable:!0,get:()=>m.FragmentsLoadError}),t.AREA_UNRESOLVED=F,t.Area=G,t.FragmentRenderer=K,t.FragmentStylesheet=C,t.FragmentsClientContext=g,t.FragmentsClientProvider=M,t.FragmentsProvider=R,t.InstanceIdContext=y,t.ModulesMapContext=E,t.ModulesMapProvider=j,t.ProjectPropertiesContext=P,t.createBrowserClient=J,t.defaultProjectProperties=S,t.injectStylesheet=A,t.useArea=T,t.useCssChunk=V,t.useFragmentComponent=w,t.useFragmentMetrics=k,t.useFragmentStylesheet=b,t.useProjectProperty=Y,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fragmentsx/runtime-react",
3
3
  "private": false,
4
- "version": "0.1.0",
4
+ "version": "0.2.0-dev-20260901074721",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@graph-state/core": "^0.13.3",
35
- "@fragmentsx/fragments-client": "0.1.0"
35
+ "@fragmentsx/fragments-client": "0.2.0-dev-20260901074721"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@testing-library/jest-dom": "^6.6.3",