@fragments-sdk/a2ui 1.0.0 → 2.0.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.
@@ -0,0 +1,47 @@
1
+ import "../chunk-QCZG5JX2.js";
2
+ import {
3
+ A2UIAudioPlayer,
4
+ A2UIButton,
5
+ A2UICard,
6
+ A2UICheckBox,
7
+ A2UIChoicePicker,
8
+ A2UIColumn,
9
+ A2UIDateTimeInput,
10
+ A2UIDivider,
11
+ A2UIIcon,
12
+ A2UIImage,
13
+ A2UIList,
14
+ A2UIModal,
15
+ A2UIRow,
16
+ A2UISlider,
17
+ A2UITabs,
18
+ A2UIText,
19
+ A2UITextField,
20
+ A2UIVideo,
21
+ fragmentsCatalog,
22
+ knownA2UIIcons,
23
+ resolveA2UIcon
24
+ } from "../chunk-FWMN7L3T.js";
25
+ export {
26
+ A2UIAudioPlayer,
27
+ A2UIButton,
28
+ A2UICard,
29
+ A2UICheckBox,
30
+ A2UIChoicePicker,
31
+ A2UIColumn,
32
+ A2UIDateTimeInput,
33
+ A2UIDivider,
34
+ A2UIIcon,
35
+ A2UIImage,
36
+ A2UIList,
37
+ A2UIModal,
38
+ A2UIRow,
39
+ A2UISlider,
40
+ A2UITabs,
41
+ A2UIText,
42
+ A2UITextField,
43
+ A2UIVideo,
44
+ fragmentsCatalog,
45
+ knownA2UIIcons,
46
+ resolveA2UIcon
47
+ };
@@ -0,0 +1,919 @@
1
+ // src/catalog/adapters/_shared.ts
2
+ import { resolvePointerPath } from "@uiprotocol/a2ui/core";
3
+ function isRecord(value) {
4
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+ function isBoundValue(value) {
7
+ return isRecord(value) && typeof value.path === "string";
8
+ }
9
+ function getProp(component, keys) {
10
+ for (const key of keys) {
11
+ if (key in component) {
12
+ return component[key];
13
+ }
14
+ }
15
+ return void 0;
16
+ }
17
+ function resolveProp(props, keys, fallback) {
18
+ const raw = getProp(props.component, keys);
19
+ if (raw === void 0) {
20
+ return fallback;
21
+ }
22
+ return props.resolve(raw);
23
+ }
24
+ function resolveString(props, keys, fallback = "") {
25
+ const value = resolveProp(props, keys);
26
+ if (value === void 0 || value === null) {
27
+ return fallback;
28
+ }
29
+ if (typeof value === "object") {
30
+ return fallback;
31
+ }
32
+ return String(value);
33
+ }
34
+ function resolveNumber(props, keys, fallback = 0) {
35
+ const value = resolveProp(props, keys);
36
+ if (typeof value === "number" && Number.isFinite(value)) {
37
+ return value;
38
+ }
39
+ if (typeof value === "string") {
40
+ const parsed = Number(value);
41
+ if (!Number.isNaN(parsed) && Number.isFinite(parsed)) {
42
+ return parsed;
43
+ }
44
+ }
45
+ return fallback;
46
+ }
47
+ function resolveBoolean(props, keys, fallback = false) {
48
+ const value = resolveProp(props, keys);
49
+ if (typeof value === "boolean") {
50
+ return value;
51
+ }
52
+ return fallback;
53
+ }
54
+ function resolveArray(props, keys) {
55
+ const value = resolveProp(props, keys);
56
+ return Array.isArray(value) ? value : [];
57
+ }
58
+ function resolveBindingPath(props, keys = ["value", "path", "valuePath", "bindingPath", "bindPath"]) {
59
+ const isPathString = (value, key) => key.toLowerCase().includes("path") || value.startsWith("/") || value.startsWith("./") || value.startsWith("../");
60
+ for (const key of keys) {
61
+ const raw = props.component[key];
62
+ if (typeof raw === "string") {
63
+ if (isPathString(raw, key)) {
64
+ return resolvePointerPath(raw, props.scopePath);
65
+ }
66
+ continue;
67
+ }
68
+ if (isBoundValue(raw)) {
69
+ return resolvePointerPath(raw.path, props.scopePath);
70
+ }
71
+ }
72
+ return void 0;
73
+ }
74
+ function resolveAction(component, keys) {
75
+ for (const key of keys) {
76
+ const raw = component[key];
77
+ if (!raw) {
78
+ continue;
79
+ }
80
+ if (typeof raw === "string") {
81
+ return { event: raw };
82
+ }
83
+ if (isRecord(raw) && typeof raw.event === "string") {
84
+ return raw;
85
+ }
86
+ }
87
+ const actions = component.actions;
88
+ if (isRecord(actions)) {
89
+ for (const key of keys) {
90
+ const candidate = actions[key];
91
+ if (isRecord(candidate) && typeof candidate.event === "string") {
92
+ return candidate;
93
+ }
94
+ }
95
+ }
96
+ return void 0;
97
+ }
98
+ function dispatchAction(props, action, options) {
99
+ if (!action) {
100
+ return false;
101
+ }
102
+ return props.dispatchAction(action, {
103
+ componentId: props.component.id,
104
+ value: options?.value,
105
+ checks: options?.checks,
106
+ pattern: options?.pattern
107
+ });
108
+ }
109
+ function getChecks(component) {
110
+ return Array.isArray(component.checks) ? component.checks : void 0;
111
+ }
112
+ function getPattern(component) {
113
+ return typeof component.pattern === "string" ? component.pattern : void 0;
114
+ }
115
+ function getPrimaryText(props, fallback = "") {
116
+ const text = resolveString(props, ["text", "label", "title", "content", "value"], fallback);
117
+ return text;
118
+ }
119
+ function mapAlign(value) {
120
+ switch (value.toLowerCase()) {
121
+ case "start":
122
+ case "left":
123
+ case "top":
124
+ return "start";
125
+ case "center":
126
+ return "center";
127
+ case "end":
128
+ case "right":
129
+ case "bottom":
130
+ return "end";
131
+ case "stretch":
132
+ return "stretch";
133
+ case "baseline":
134
+ return "baseline";
135
+ default:
136
+ return void 0;
137
+ }
138
+ }
139
+ function mapJustify(value) {
140
+ switch (value.toLowerCase()) {
141
+ case "start":
142
+ case "left":
143
+ case "top":
144
+ return "start";
145
+ case "center":
146
+ return "center";
147
+ case "end":
148
+ case "right":
149
+ case "bottom":
150
+ return "end";
151
+ case "between":
152
+ case "spacebetween":
153
+ return "between";
154
+ default:
155
+ return void 0;
156
+ }
157
+ }
158
+ function toStringArray(value) {
159
+ if (!Array.isArray(value)) {
160
+ return [];
161
+ }
162
+ return value.map((entry) => entry === void 0 || entry === null ? "" : String(entry)).filter((entry) => entry.length > 0);
163
+ }
164
+
165
+ // src/catalog/adapters/audio-player.tsx
166
+ import { jsx } from "react/jsx-runtime";
167
+ function A2UIAudioPlayer(props) {
168
+ const src = resolveString(props, ["src", "url"]);
169
+ const controls = resolveBoolean(props, ["controls"], true);
170
+ const autoplay = resolveBoolean(props, ["autoplay"], false);
171
+ const loop = resolveBoolean(props, ["loop"], false);
172
+ return /* @__PURE__ */ jsx("audio", { src, controls, autoPlay: autoplay, loop, style: { width: "100%" } });
173
+ }
174
+
175
+ // src/catalog/adapters/button.tsx
176
+ import { Button } from "@fragments-sdk/ui";
177
+ import { jsx as jsx2 } from "react/jsx-runtime";
178
+ function mapButtonVariant(style) {
179
+ switch (style.toLowerCase()) {
180
+ case "borderless":
181
+ case "ghost":
182
+ return "ghost";
183
+ case "outlined":
184
+ case "outline":
185
+ return "outlined";
186
+ case "danger":
187
+ case "destructive":
188
+ return "danger";
189
+ case "icon":
190
+ return "icon";
191
+ case "secondary":
192
+ return "secondary";
193
+ default:
194
+ return "primary";
195
+ }
196
+ }
197
+ function A2UIButton(props) {
198
+ const variant = mapButtonVariant(resolveString(props, ["style", "variant"], "primary"));
199
+ const label = getPrimaryText(props, "Button");
200
+ const disabled = resolveBoolean(props, ["disabled", "enabled"], false);
201
+ const action = resolveAction(props.component, ["action", "onClickAction", "onPressAction", "submitAction"]);
202
+ const checks = getChecks(props.component);
203
+ const pattern = getPattern(props.component);
204
+ const children = props.childIds.length > 0 ? props.childIds.map((childId) => props.renderChild(childId, `${props.component.id}:${childId}`)) : null;
205
+ return /* @__PURE__ */ jsx2(
206
+ Button,
207
+ {
208
+ variant,
209
+ disabled,
210
+ onClick: () => {
211
+ dispatchAction(props, action, {
212
+ checks,
213
+ pattern,
214
+ value: props.resolve(props.component.value)
215
+ });
216
+ },
217
+ children: children ?? label
218
+ }
219
+ );
220
+ }
221
+
222
+ // src/catalog/adapters/card.tsx
223
+ import { Card } from "@fragments-sdk/ui";
224
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
225
+ function A2UICard(props) {
226
+ const title = resolveString(props, ["title"], "");
227
+ const description = resolveString(props, ["description", "subtitle"], "");
228
+ return /* @__PURE__ */ jsxs(Card, { children: [
229
+ (title || description) && /* @__PURE__ */ jsxs(Card.Header, { children: [
230
+ title && /* @__PURE__ */ jsx3(Card.Title, { children: title }),
231
+ description && /* @__PURE__ */ jsx3(Card.Description, { children: description })
232
+ ] }),
233
+ /* @__PURE__ */ jsx3(Card.Body, { children: props.renderedChildren })
234
+ ] });
235
+ }
236
+
237
+ // src/catalog/adapters/checkbox.tsx
238
+ import { Checkbox } from "@fragments-sdk/ui";
239
+ import { jsx as jsx4 } from "react/jsx-runtime";
240
+ function A2UICheckBox(props) {
241
+ const path = resolveBindingPath(props, ["value", "path", "valuePath", "bindingPath"]);
242
+ const resolved = path ? props.resolve({ path }) : props.resolve(props.component.value);
243
+ const checked = Boolean(resolved);
244
+ const label = resolveString(props, ["label", "text"], "");
245
+ const description = resolveString(props, ["description"], "");
246
+ const disabled = resolveBoolean(props, ["disabled", "enabled"], false);
247
+ const action = resolveAction(props.component, ["onChangeAction", "action"]);
248
+ const checks = getChecks(props.component);
249
+ const pattern = getPattern(props.component);
250
+ return /* @__PURE__ */ jsx4(
251
+ Checkbox,
252
+ {
253
+ checked,
254
+ label: label || void 0,
255
+ description: description || void 0,
256
+ disabled,
257
+ onCheckedChange: (nextValue) => {
258
+ if (path) {
259
+ props.setData(path, nextValue);
260
+ }
261
+ dispatchAction(props, action, {
262
+ checks,
263
+ pattern,
264
+ value: nextValue
265
+ });
266
+ }
267
+ }
268
+ );
269
+ }
270
+
271
+ // src/catalog/adapters/choice-picker.tsx
272
+ import { Checkbox as Checkbox2, Chip, RadioGroup, Select, Stack } from "@fragments-sdk/ui";
273
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
274
+ function resolveOptions(props) {
275
+ const options = resolveArray(props, ["options", "items", "choices"]);
276
+ const resolved = [];
277
+ options.forEach((option, index) => {
278
+ if (!isRecord(option)) {
279
+ return;
280
+ }
281
+ const valueRaw = option.value ?? option.id ?? index;
282
+ const labelRaw = option.label ?? option.title ?? option.text ?? valueRaw;
283
+ resolved.push({
284
+ value: String(props.resolve(valueRaw) ?? valueRaw),
285
+ label: String(props.resolve(labelRaw) ?? labelRaw),
286
+ description: option.description ? String(props.resolve(option.description) ?? option.description) : void 0
287
+ });
288
+ });
289
+ return resolved;
290
+ }
291
+ function nextMultiValues(currentValues, value, checked) {
292
+ if (checked) {
293
+ return currentValues.includes(value) ? currentValues : [...currentValues, value];
294
+ }
295
+ return currentValues.filter((entry) => entry !== value);
296
+ }
297
+ function shouldUseSelect(displayStyle) {
298
+ const normalized = displayStyle.toLowerCase();
299
+ return normalized === "select" || normalized === "dropdown" || normalized === "menu";
300
+ }
301
+ function A2UIChoicePicker(props) {
302
+ const options = resolveOptions(props);
303
+ const path = resolveBindingPath(props, ["value", "path", "valuePath", "bindingPath"]);
304
+ const rawValue = path ? props.resolve({ path }) : props.resolve(props.component.value);
305
+ const maxAllowedSelections = resolveNumber(props, ["maxAllowedSelections"], 1);
306
+ const mutuallyExclusive = resolveBoolean(props, ["mutuallyExclusive"], maxAllowedSelections === 1);
307
+ const isSingleSelection = mutuallyExclusive || maxAllowedSelections <= 1;
308
+ const displayStyle = resolveString(props, ["displayStyle"], isSingleSelection ? "radio" : "checkbox");
309
+ const action = resolveAction(props.component, ["onChangeAction", "action"]);
310
+ const checks = getChecks(props.component);
311
+ const pattern = getPattern(props.component);
312
+ const commitValue = (nextValue) => {
313
+ if (path) {
314
+ props.setData(path, nextValue);
315
+ }
316
+ dispatchAction(props, action, {
317
+ checks,
318
+ pattern,
319
+ value: nextValue
320
+ });
321
+ };
322
+ if (isSingleSelection) {
323
+ const selectedValue = rawValue === void 0 || rawValue === null ? "" : String(rawValue);
324
+ if (shouldUseSelect(displayStyle)) {
325
+ return /* @__PURE__ */ jsxs2(Select, { value: selectedValue || null, onValueChange: (nextValue) => commitValue(nextValue ?? ""), children: [
326
+ /* @__PURE__ */ jsx5(Select.Trigger, { placeholder: "Choose an option" }),
327
+ /* @__PURE__ */ jsx5(Select.Content, { children: options.map((option) => /* @__PURE__ */ jsx5(Select.Item, { value: option.value, children: option.label }, option.value)) })
328
+ ] });
329
+ }
330
+ return /* @__PURE__ */ jsx5(RadioGroup, { value: selectedValue, onValueChange: (nextValue) => commitValue(nextValue), children: options.map((option) => /* @__PURE__ */ jsx5(
331
+ RadioGroup.Item,
332
+ {
333
+ value: option.value,
334
+ label: option.label,
335
+ description: option.description
336
+ },
337
+ option.value
338
+ )) });
339
+ }
340
+ const selectedValues = toStringArray(rawValue);
341
+ const normalizedStyle = displayStyle.toLowerCase();
342
+ if (normalizedStyle === "chips") {
343
+ return /* @__PURE__ */ jsx5(
344
+ Chip.Group,
345
+ {
346
+ value: selectedValues,
347
+ onChange: (nextValues) => {
348
+ commitValue(nextValues);
349
+ },
350
+ children: options.map((option) => /* @__PURE__ */ jsx5(Chip, { value: option.value, children: option.label }, option.value))
351
+ }
352
+ );
353
+ }
354
+ return /* @__PURE__ */ jsx5(Stack, { direction: "column", gap: "sm", children: options.map((option) => /* @__PURE__ */ jsx5(
355
+ Checkbox2,
356
+ {
357
+ label: option.label,
358
+ description: option.description,
359
+ checked: selectedValues.includes(option.value),
360
+ onCheckedChange: (checked) => {
361
+ const nextValues = nextMultiValues(selectedValues, option.value, checked);
362
+ commitValue(nextValues);
363
+ }
364
+ },
365
+ option.value
366
+ )) });
367
+ }
368
+
369
+ // src/catalog/adapters/column.tsx
370
+ import { Stack as Stack2 } from "@fragments-sdk/ui";
371
+ import { jsx as jsx6 } from "react/jsx-runtime";
372
+ function A2UIColumn(props) {
373
+ const align = mapAlign(resolveString(props, ["alignment", "alignItems"], ""));
374
+ const justify = mapJustify(resolveString(props, ["distribution", "justifyContent"], ""));
375
+ return /* @__PURE__ */ jsx6(Stack2, { direction: "column", align, justify, children: props.renderedChildren });
376
+ }
377
+
378
+ // src/catalog/adapters/date-time-input.tsx
379
+ import { DatePicker, Stack as Stack3, Text } from "@fragments-sdk/ui";
380
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
381
+ function normalizeMode(raw) {
382
+ const mode = raw.toLowerCase();
383
+ if (mode === "time" || mode === "timeonly" || mode === "time-only") {
384
+ return "time";
385
+ }
386
+ if (mode.includes("datetime") || mode.includes("date-time") || mode === "both") {
387
+ return "datetime";
388
+ }
389
+ return "date";
390
+ }
391
+ function toDateValue(raw) {
392
+ if (!raw) {
393
+ return null;
394
+ }
395
+ const parsed = new Date(raw);
396
+ if (Number.isNaN(parsed.getTime())) {
397
+ return null;
398
+ }
399
+ return parsed;
400
+ }
401
+ function toDateOnlyString(date) {
402
+ return date.toISOString().slice(0, 10);
403
+ }
404
+ function toDateTimeLocalValue(raw) {
405
+ if (!raw) {
406
+ return "";
407
+ }
408
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(raw)) {
409
+ return raw.slice(0, 16);
410
+ }
411
+ const parsed = new Date(raw);
412
+ if (Number.isNaN(parsed.getTime())) {
413
+ return raw;
414
+ }
415
+ return parsed.toISOString().slice(0, 16);
416
+ }
417
+ function A2UIDateTimeInput(props) {
418
+ const mode = normalizeMode(resolveString(props, ["dateTimeType", "type", "inputType", "mode"], "date"));
419
+ const path = resolveBindingPath(props, ["value", "path", "valuePath", "bindingPath"]);
420
+ const resolvedValue = path ? props.resolve({ path }) : props.resolve(props.component.value);
421
+ const value = resolvedValue ?? "";
422
+ const label = resolveString(props, ["label", "title"], "");
423
+ const placeholder = resolveString(props, ["placeholder"], mode === "date" ? "Select date" : "");
424
+ const action = resolveAction(props.component, ["onChangeAction", "action"]);
425
+ const checks = getChecks(props.component);
426
+ const pattern = getPattern(props.component);
427
+ const commitValue = (nextValue) => {
428
+ if (path) {
429
+ props.setData(path, nextValue);
430
+ }
431
+ dispatchAction(props, action, {
432
+ checks,
433
+ pattern,
434
+ value: nextValue
435
+ });
436
+ };
437
+ if (mode === "date") {
438
+ const selectedDate = toDateValue(value);
439
+ return /* @__PURE__ */ jsxs3(Stack3, { direction: "column", gap: "xs", children: [
440
+ label && /* @__PURE__ */ jsx7(Text, { as: "label", children: label }),
441
+ /* @__PURE__ */ jsxs3(
442
+ DatePicker,
443
+ {
444
+ mode: "single",
445
+ selected: selectedDate,
446
+ onSelect: (nextDate) => {
447
+ if (!nextDate) {
448
+ commitValue("");
449
+ return;
450
+ }
451
+ commitValue(toDateOnlyString(nextDate));
452
+ },
453
+ placeholder,
454
+ children: [
455
+ /* @__PURE__ */ jsx7(DatePicker.Trigger, { placeholder }),
456
+ /* @__PURE__ */ jsx7(DatePicker.Content, { children: /* @__PURE__ */ jsx7(DatePicker.Calendar, {}) })
457
+ ]
458
+ }
459
+ )
460
+ ] });
461
+ }
462
+ if (mode === "time") {
463
+ return /* @__PURE__ */ jsxs3(Stack3, { direction: "column", gap: "xs", children: [
464
+ label && /* @__PURE__ */ jsx7(Text, { as: "label", children: label }),
465
+ /* @__PURE__ */ jsx7(
466
+ "input",
467
+ {
468
+ type: "time",
469
+ value,
470
+ onChange: (event) => {
471
+ const timeValue = event.currentTarget.value;
472
+ const normalized = timeValue && !timeValue.includes("T") ? `${timeValue}${timeValue.split(":").length < 3 ? ":00" : ""}` : timeValue;
473
+ commitValue(normalized);
474
+ }
475
+ }
476
+ )
477
+ ] });
478
+ }
479
+ return /* @__PURE__ */ jsxs3(Stack3, { direction: "column", gap: "xs", children: [
480
+ label && /* @__PURE__ */ jsx7(Text, { as: "label", children: label }),
481
+ /* @__PURE__ */ jsx7(
482
+ "input",
483
+ {
484
+ type: "datetime-local",
485
+ value: toDateTimeLocalValue(value),
486
+ onChange: (event) => commitValue(event.currentTarget.value)
487
+ }
488
+ )
489
+ ] });
490
+ }
491
+
492
+ // src/catalog/adapters/divider.tsx
493
+ import { Separator } from "@fragments-sdk/ui";
494
+ import { jsx as jsx8 } from "react/jsx-runtime";
495
+ function A2UIDivider(props) {
496
+ const orientation = resolveString(props, ["orientation"], "horizontal").toLowerCase() === "vertical" ? "vertical" : "horizontal";
497
+ return /* @__PURE__ */ jsx8(Separator, { orientation });
498
+ }
499
+
500
+ // src/catalog/icon-map.ts
501
+ import * as PhosphorIcons from "@phosphor-icons/react";
502
+ var manualMap = {
503
+ add: "Plus",
504
+ alert: "WarningCircle",
505
+ arrowdown: "ArrowDown",
506
+ arrowleft: "ArrowLeft",
507
+ arrowright: "ArrowRight",
508
+ arrowup: "ArrowUp",
509
+ attachment: "Paperclip",
510
+ bell: "Bell",
511
+ bookmark: "BookmarkSimple",
512
+ calendar: "Calendar",
513
+ camera: "Camera",
514
+ chat: "ChatCircle",
515
+ check: "Check",
516
+ checkcircle: "CheckCircle",
517
+ chevrondown: "CaretDown",
518
+ chevronleft: "CaretLeft",
519
+ chevronright: "CaretRight",
520
+ chevronup: "CaretUp",
521
+ close: "X",
522
+ copy: "Copy",
523
+ dashboard: "SquaresFour",
524
+ delete: "Trash",
525
+ download: "DownloadSimple",
526
+ edit: "PencilSimple",
527
+ error: "XCircle",
528
+ external: "ArrowSquareOut",
529
+ eye: "Eye",
530
+ filter: "FunnelSimple",
531
+ folder: "FolderSimple",
532
+ gear: "Gear",
533
+ globe: "GlobeHemisphereWest",
534
+ heart: "Heart",
535
+ help: "Question",
536
+ home: "House",
537
+ image: "ImageSquare",
538
+ info: "Info",
539
+ link: "LinkSimple",
540
+ lock: "LockSimple",
541
+ logout: "SignOut",
542
+ menu: "List",
543
+ message: "ChatText",
544
+ minus: "Minus",
545
+ moon: "Moon",
546
+ notification: "BellSimple",
547
+ phone: "Phone",
548
+ plus: "Plus",
549
+ refresh: "ArrowsClockwise",
550
+ search: "MagnifyingGlass",
551
+ send: "PaperPlaneTilt",
552
+ settings: "GearSix",
553
+ share: "ShareNetwork",
554
+ star: "Star",
555
+ sun: "Sun",
556
+ success: "CheckCircle",
557
+ time: "Clock",
558
+ upload: "UploadSimple",
559
+ user: "User",
560
+ users: "Users",
561
+ warning: "Warning"
562
+ };
563
+ function toLookupKey(name) {
564
+ return name.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
565
+ }
566
+ function toPascalCase(name) {
567
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()).join("");
568
+ }
569
+ function resolveA2UIcon(name) {
570
+ const normalized = toLookupKey(name);
571
+ const mapped = manualMap[normalized] ?? toPascalCase(name);
572
+ const icons = PhosphorIcons;
573
+ return icons[mapped] ?? null;
574
+ }
575
+ var knownA2UIIcons = Object.keys(manualMap);
576
+
577
+ // src/catalog/adapters/icon.tsx
578
+ import { Icon } from "@fragments-sdk/ui";
579
+ import { jsx as jsx9 } from "react/jsx-runtime";
580
+ function A2UIIcon(props) {
581
+ const iconName = resolveString(props, ["name", "icon", "iconName"]);
582
+ const iconComponent = resolveA2UIcon(iconName);
583
+ if (!iconComponent) {
584
+ if (iconName) {
585
+ console.warn(`[A2UI] Unknown icon "${iconName}" \u2014 no mapping found in icon-map`);
586
+ }
587
+ return /* @__PURE__ */ jsx9("span", { "data-a2ui-icon-fallback": iconName, "aria-hidden": "true" });
588
+ }
589
+ return /* @__PURE__ */ jsx9(Icon, { icon: iconComponent });
590
+ }
591
+
592
+ // src/catalog/adapters/image.tsx
593
+ import { Avatar, Image as FragmentsImage } from "@fragments-sdk/ui";
594
+ import { jsx as jsx10 } from "react/jsx-runtime";
595
+ var AVATAR_HINTS = /* @__PURE__ */ new Set(["avatar", "profile", "user", "person"]);
596
+ function A2UIImage(props) {
597
+ const src = resolveString(props, ["url", "src"]);
598
+ const alt = resolveString(props, ["alt", "label", "description"]);
599
+ const usageHint = resolveString(props, ["usageHint"], "").toLowerCase();
600
+ if (AVATAR_HINTS.has(usageHint)) {
601
+ const size = resolveNumber(props, ["size"], 40);
602
+ return /* @__PURE__ */ jsx10(Avatar, { src, alt, customSize: size, name: alt });
603
+ }
604
+ return /* @__PURE__ */ jsx10(FragmentsImage, { src, alt: alt || "image" });
605
+ }
606
+
607
+ // src/catalog/adapters/list.tsx
608
+ import { Stack as Stack4 } from "@fragments-sdk/ui";
609
+ import { resolvePointerPath as resolvePointerPath2 } from "@uiprotocol/a2ui/core";
610
+ import { jsx as jsx11 } from "react/jsx-runtime";
611
+ function resolveTemplateBasePath(props, template) {
612
+ const templatePath = template.path;
613
+ if (typeof templatePath === "string") {
614
+ return resolvePointerPath2(templatePath, props.scopePath);
615
+ }
616
+ const items = props.component.items;
617
+ if (isBoundValue(items)) {
618
+ return resolvePointerPath2(items.path, props.scopePath);
619
+ }
620
+ if (typeof props.component.path === "string") {
621
+ return resolvePointerPath2(props.component.path, props.scopePath);
622
+ }
623
+ return props.scopePath;
624
+ }
625
+ function A2UIList(props) {
626
+ const template = isRecord(props.component.template) ? props.component.template : void 0;
627
+ const templateChild = template && typeof template.child === "string" ? template.child : void 0;
628
+ if (template && templateChild) {
629
+ const items = resolveArray(props, ["items", "data", "source"]);
630
+ const basePath = resolveTemplateBasePath(props, template);
631
+ return /* @__PURE__ */ jsx11(Stack4, { direction: "column", gap: "sm", children: items.map(
632
+ (_, index) => props.renderChild(templateChild, `${props.component.id}:${templateChild}:${index}`, `${basePath}/${index}`)
633
+ ) });
634
+ }
635
+ const orientation = resolveString(props, ["orientation"], "vertical").toLowerCase();
636
+ return /* @__PURE__ */ jsx11(Stack4, { direction: orientation === "horizontal" ? "row" : "column", gap: "sm", children: props.renderedChildren });
637
+ }
638
+
639
+ // src/catalog/adapters/modal.tsx
640
+ import * as React from "react";
641
+ import { Button as Button2, Dialog } from "@fragments-sdk/ui";
642
+ import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
643
+ function buildTriggerNode(props, childId) {
644
+ if (childId) {
645
+ const rendered = props.renderChild(childId, `${props.component.id}:trigger`);
646
+ if (React.isValidElement(rendered)) {
647
+ return rendered;
648
+ }
649
+ }
650
+ const triggerLabel = resolveString(props, ["triggerLabel", "entryPointLabel", "label"], "Open");
651
+ return /* @__PURE__ */ jsx12(Button2, { variant: "secondary", children: triggerLabel });
652
+ }
653
+ function A2UIModal(props) {
654
+ const entryPointChild = typeof props.component.entryPointChild === "string" ? props.component.entryPointChild : void 0;
655
+ const contentChild = typeof props.component.contentChild === "string" ? props.component.contentChild : void 0;
656
+ const openProp = resolveBoolean(props, ["open"], false);
657
+ const title = resolveString(props, ["title"], "");
658
+ const description = resolveString(props, ["description"], "");
659
+ const [open, setOpen] = React.useState(openProp);
660
+ React.useEffect(() => {
661
+ setOpen(openProp);
662
+ }, [openProp]);
663
+ return /* @__PURE__ */ jsxs4(Dialog, { open, onOpenChange: setOpen, children: [
664
+ /* @__PURE__ */ jsx12(Dialog.Trigger, { asChild: true, children: buildTriggerNode(props, entryPointChild) }),
665
+ /* @__PURE__ */ jsxs4(Dialog.Content, { children: [
666
+ (title || description) && /* @__PURE__ */ jsxs4(Dialog.Header, { children: [
667
+ title && /* @__PURE__ */ jsx12(Dialog.Title, { children: title }),
668
+ description && /* @__PURE__ */ jsx12(Dialog.Description, { children: description })
669
+ ] }),
670
+ /* @__PURE__ */ jsx12(Dialog.Body, { children: contentChild ? props.renderChild(contentChild, `${props.component.id}:${contentChild}`) : props.renderedChildren }),
671
+ /* @__PURE__ */ jsx12(Dialog.Close, {})
672
+ ] })
673
+ ] });
674
+ }
675
+
676
+ // src/catalog/adapters/row.tsx
677
+ import { Stack as Stack5 } from "@fragments-sdk/ui";
678
+ import { jsx as jsx13 } from "react/jsx-runtime";
679
+ function A2UIRow(props) {
680
+ const align = mapAlign(resolveString(props, ["alignment", "alignItems"], ""));
681
+ const justify = mapJustify(resolveString(props, ["distribution", "justifyContent"], ""));
682
+ return /* @__PURE__ */ jsx13(Stack5, { direction: "row", align, justify, children: props.renderedChildren });
683
+ }
684
+
685
+ // src/catalog/adapters/slider.tsx
686
+ import { Slider } from "@fragments-sdk/ui";
687
+ import { jsx as jsx14 } from "react/jsx-runtime";
688
+ function A2UISlider(props) {
689
+ const path = resolveBindingPath(props, ["value", "path", "valuePath", "bindingPath"]);
690
+ const resolvedValue = path ? props.resolve({ path }) : props.resolve(props.component.value);
691
+ const min = resolveNumber(props, ["minValue", "min"], 0);
692
+ const max = resolveNumber(props, ["maxValue", "max"], 100);
693
+ const step = resolveNumber(props, ["step"], 1);
694
+ const value = resolvedValue === null || resolvedValue === void 0 ? void 0 : typeof resolvedValue === "number" ? resolvedValue : min;
695
+ const label = resolveString(props, ["label", "title"], "");
696
+ const action = resolveAction(props.component, ["onChangeAction", "action"]);
697
+ const checks = getChecks(props.component);
698
+ const pattern = getPattern(props.component);
699
+ return /* @__PURE__ */ jsx14(
700
+ Slider,
701
+ {
702
+ label: label || void 0,
703
+ value,
704
+ min,
705
+ max,
706
+ step,
707
+ onChange: (nextValue) => {
708
+ if (path) {
709
+ props.setData(path, nextValue);
710
+ }
711
+ dispatchAction(props, action, {
712
+ checks,
713
+ pattern,
714
+ value: nextValue
715
+ });
716
+ }
717
+ }
718
+ );
719
+ }
720
+
721
+ // src/catalog/adapters/tabs.tsx
722
+ import { Tabs } from "@fragments-sdk/ui";
723
+ import { Fragment, jsx as jsx15, jsxs as jsxs5 } from "react/jsx-runtime";
724
+ function resolveTabItems(props) {
725
+ const items = resolveArray(props, ["tabItems", "items"]);
726
+ const resolved = [];
727
+ items.forEach((item, index) => {
728
+ if (!isRecord(item)) {
729
+ return;
730
+ }
731
+ const value = item.value;
732
+ const key = value !== void 0 ? String(value) : String(index);
733
+ const titleRaw = item.title ?? item.label ?? `Tab ${index + 1}`;
734
+ const title = String(props.resolve(titleRaw) ?? `Tab ${index + 1}`);
735
+ const childId = typeof item.child === "string" ? item.child : typeof item.panelChild === "string" ? item.panelChild : void 0;
736
+ resolved.push({
737
+ key,
738
+ title,
739
+ childId
740
+ });
741
+ });
742
+ return resolved;
743
+ }
744
+ function A2UITabs(props) {
745
+ const items = resolveTabItems(props);
746
+ if (items.length === 0) {
747
+ return /* @__PURE__ */ jsx15(Fragment, { children: props.renderedChildren });
748
+ }
749
+ return /* @__PURE__ */ jsxs5(Tabs, { defaultValue: items[0].key, children: [
750
+ /* @__PURE__ */ jsx15(Tabs.List, { children: items.map((item) => /* @__PURE__ */ jsx15(Tabs.Tab, { value: item.key, children: item.title }, `tab-${item.key}`)) }),
751
+ items.map((item) => /* @__PURE__ */ jsx15(Tabs.Panel, { value: item.key, children: item.childId ? props.renderChild(item.childId, `${props.component.id}:${item.childId}`) : null }, `panel-${item.key}`))
752
+ ] });
753
+ }
754
+
755
+ // src/catalog/adapters/text.tsx
756
+ import { Text as Text2 } from "@fragments-sdk/ui";
757
+ import { jsx as jsx16 } from "react/jsx-runtime";
758
+ var TEXT_VARIANT_MAP = {
759
+ h1: "h1",
760
+ h2: "h2",
761
+ h3: "h3",
762
+ h4: "h4",
763
+ h5: "h5",
764
+ h6: "h6",
765
+ heading1: "h1",
766
+ heading2: "h2",
767
+ heading3: "h3",
768
+ heading4: "h4",
769
+ heading5: "h5",
770
+ heading6: "h6",
771
+ body: "p",
772
+ paragraph: "p",
773
+ caption: "span"
774
+ };
775
+ function mapTextElement(variant) {
776
+ return TEXT_VARIANT_MAP[variant.toLowerCase()] ?? "span";
777
+ }
778
+ function A2UIText(props) {
779
+ const variant = resolveString(props, ["variant", "textVariant"], "body");
780
+ const as = mapTextElement(variant);
781
+ const text = getPrimaryText(props);
782
+ const content = text.length > 0 ? text : props.renderedChildren;
783
+ return /* @__PURE__ */ jsx16(Text2, { as, children: content });
784
+ }
785
+
786
+ // src/catalog/adapters/text-field.tsx
787
+ import { Input, Textarea } from "@fragments-sdk/ui";
788
+ import { jsx as jsx17 } from "react/jsx-runtime";
789
+ function mapInputType(raw) {
790
+ switch (raw.toLowerCase()) {
791
+ case "email":
792
+ return "email";
793
+ case "password":
794
+ return "password";
795
+ case "number":
796
+ return "number";
797
+ case "tel":
798
+ case "phone":
799
+ return "tel";
800
+ case "url":
801
+ return "url";
802
+ default:
803
+ return "text";
804
+ }
805
+ }
806
+ function A2UITextField(props) {
807
+ const textFieldType = resolveString(props, ["textFieldType", "type"], "shortText");
808
+ const isLongText = textFieldType.toLowerCase() === "longtext" || textFieldType.toLowerCase() === "textarea";
809
+ const bindingPath = resolveBindingPath(props, ["value", "path", "valuePath", "bindingPath"]);
810
+ const resolvedValue = bindingPath ? props.resolve({ path: bindingPath }) : props.resolve(props.component.value);
811
+ const value = resolvedValue ?? "";
812
+ const label = resolveString(props, ["label", "title"], "");
813
+ const placeholder = resolveString(props, ["placeholder"], "");
814
+ const disabled = resolveBoolean(props, ["disabled", "enabled"], false);
815
+ const action = resolveAction(props.component, ["onChangeAction", "action", "onSubmitAction"]);
816
+ const checks = getChecks(props.component);
817
+ const pattern = getPattern(props.component);
818
+ const onChange = (nextValue) => {
819
+ if (bindingPath) {
820
+ props.setData(bindingPath, nextValue);
821
+ }
822
+ dispatchAction(props, action, {
823
+ checks,
824
+ pattern,
825
+ value: nextValue
826
+ });
827
+ };
828
+ if (isLongText) {
829
+ return /* @__PURE__ */ jsx17(
830
+ Textarea,
831
+ {
832
+ value,
833
+ label: label || void 0,
834
+ placeholder: placeholder || void 0,
835
+ disabled,
836
+ onChange
837
+ }
838
+ );
839
+ }
840
+ const inputType = mapInputType(textFieldType);
841
+ return /* @__PURE__ */ jsx17(
842
+ Input,
843
+ {
844
+ type: inputType,
845
+ value,
846
+ label: label || void 0,
847
+ placeholder: placeholder || void 0,
848
+ disabled,
849
+ onChange
850
+ }
851
+ );
852
+ }
853
+
854
+ // src/catalog/adapters/video.tsx
855
+ import { jsx as jsx18 } from "react/jsx-runtime";
856
+ function A2UIVideo(props) {
857
+ const src = resolveString(props, ["src", "url"]);
858
+ const controls = resolveBoolean(props, ["controls"], true);
859
+ const autoplay = resolveBoolean(props, ["autoplay"], false);
860
+ const loop = resolveBoolean(props, ["loop"], false);
861
+ const muted = resolveBoolean(props, ["muted"], false);
862
+ return /* @__PURE__ */ jsx18(
863
+ "video",
864
+ {
865
+ src,
866
+ controls,
867
+ autoPlay: autoplay,
868
+ loop,
869
+ muted,
870
+ style: { width: "100%", maxWidth: "100%" }
871
+ }
872
+ );
873
+ }
874
+
875
+ // src/catalog/component-map.ts
876
+ var fragmentsCatalog = {
877
+ Text: A2UIText,
878
+ Image: A2UIImage,
879
+ Icon: A2UIIcon,
880
+ Video: A2UIVideo,
881
+ AudioPlayer: A2UIAudioPlayer,
882
+ Row: A2UIRow,
883
+ Column: A2UIColumn,
884
+ List: A2UIList,
885
+ Card: A2UICard,
886
+ Tabs: A2UITabs,
887
+ Modal: A2UIModal,
888
+ Divider: A2UIDivider,
889
+ Button: A2UIButton,
890
+ TextField: A2UITextField,
891
+ CheckBox: A2UICheckBox,
892
+ ChoicePicker: A2UIChoicePicker,
893
+ Slider: A2UISlider,
894
+ DateTimeInput: A2UIDateTimeInput
895
+ };
896
+
897
+ export {
898
+ A2UIAudioPlayer,
899
+ A2UIButton,
900
+ A2UICard,
901
+ A2UICheckBox,
902
+ A2UIChoicePicker,
903
+ A2UIColumn,
904
+ A2UIDateTimeInput,
905
+ A2UIDivider,
906
+ resolveA2UIcon,
907
+ knownA2UIIcons,
908
+ A2UIIcon,
909
+ A2UIImage,
910
+ A2UIList,
911
+ A2UIModal,
912
+ A2UIRow,
913
+ A2UISlider,
914
+ A2UITabs,
915
+ A2UIText,
916
+ A2UITextField,
917
+ A2UIVideo,
918
+ fragmentsCatalog
919
+ };
File without changes
@@ -0,0 +1,49 @@
1
+ import {
2
+ fragmentsCatalog
3
+ } from "./chunk-FWMN7L3T.js";
4
+
5
+ // src/react/index.ts
6
+ import {
7
+ useA2UIMessages,
8
+ useA2UISurface,
9
+ useDataBinding,
10
+ useFormBinding,
11
+ useAction
12
+ } from "@uiprotocol/a2ui/react";
13
+
14
+ // src/react/FragmentsA2UIProvider.tsx
15
+ import * as React from "react";
16
+ import { A2UIProvider } from "@uiprotocol/a2ui/react";
17
+ import { jsx } from "react/jsx-runtime";
18
+ function FragmentsA2UIProvider({ catalog, ...props }) {
19
+ const componentsMap = React.useMemo(() => {
20
+ const merged = { ...fragmentsCatalog };
21
+ if (!catalog) {
22
+ return merged;
23
+ }
24
+ for (const [key, value] of Object.entries(catalog)) {
25
+ if (value) {
26
+ merged[key] = value;
27
+ }
28
+ }
29
+ return merged;
30
+ }, [catalog]);
31
+ return /* @__PURE__ */ jsx(A2UIProvider, { ...props, componentsMap });
32
+ }
33
+
34
+ // src/react/FragmentsA2UIRenderer.tsx
35
+ import { A2UIRenderer } from "@uiprotocol/a2ui/react";
36
+ import { jsx as jsx2 } from "react/jsx-runtime";
37
+ function FragmentsA2UIRenderer(props) {
38
+ return /* @__PURE__ */ jsx2(A2UIRenderer, { ...props });
39
+ }
40
+
41
+ export {
42
+ FragmentsA2UIProvider,
43
+ FragmentsA2UIRenderer,
44
+ useA2UIMessages,
45
+ useA2UISurface,
46
+ useDataBinding,
47
+ useFormBinding,
48
+ useAction
49
+ };
package/dist/index.js ADDED
@@ -0,0 +1,63 @@
1
+ import {
2
+ FragmentsA2UIProvider,
3
+ FragmentsA2UIRenderer,
4
+ useA2UIMessages,
5
+ useA2UISurface,
6
+ useAction,
7
+ useDataBinding,
8
+ useFormBinding
9
+ } from "./chunk-TWIIXJBN.js";
10
+ import "./chunk-QCZG5JX2.js";
11
+ import {
12
+ A2UIAudioPlayer,
13
+ A2UIButton,
14
+ A2UICard,
15
+ A2UICheckBox,
16
+ A2UIChoicePicker,
17
+ A2UIColumn,
18
+ A2UIDateTimeInput,
19
+ A2UIDivider,
20
+ A2UIIcon,
21
+ A2UIImage,
22
+ A2UIList,
23
+ A2UIModal,
24
+ A2UIRow,
25
+ A2UISlider,
26
+ A2UITabs,
27
+ A2UIText,
28
+ A2UITextField,
29
+ A2UIVideo,
30
+ fragmentsCatalog,
31
+ knownA2UIIcons,
32
+ resolveA2UIcon
33
+ } from "./chunk-FWMN7L3T.js";
34
+ export {
35
+ A2UIAudioPlayer,
36
+ A2UIButton,
37
+ A2UICard,
38
+ A2UICheckBox,
39
+ A2UIChoicePicker,
40
+ A2UIColumn,
41
+ A2UIDateTimeInput,
42
+ A2UIDivider,
43
+ A2UIIcon,
44
+ A2UIImage,
45
+ A2UIList,
46
+ A2UIModal,
47
+ A2UIRow,
48
+ A2UISlider,
49
+ A2UITabs,
50
+ A2UIText,
51
+ A2UITextField,
52
+ A2UIVideo,
53
+ FragmentsA2UIProvider,
54
+ FragmentsA2UIRenderer,
55
+ fragmentsCatalog,
56
+ knownA2UIIcons,
57
+ resolveA2UIcon,
58
+ useA2UIMessages,
59
+ useA2UISurface,
60
+ useAction,
61
+ useDataBinding,
62
+ useFormBinding
63
+ };
@@ -0,0 +1,19 @@
1
+ import {
2
+ FragmentsA2UIProvider,
3
+ FragmentsA2UIRenderer,
4
+ useA2UIMessages,
5
+ useA2UISurface,
6
+ useAction,
7
+ useDataBinding,
8
+ useFormBinding
9
+ } from "../chunk-TWIIXJBN.js";
10
+ import "../chunk-FWMN7L3T.js";
11
+ export {
12
+ FragmentsA2UIProvider,
13
+ FragmentsA2UIRenderer,
14
+ useA2UIMessages,
15
+ useA2UISurface,
16
+ useAction,
17
+ useDataBinding,
18
+ useFormBinding
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragments-sdk/a2ui",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "license": "FSL-1.1-MIT",
5
5
  "description": "Fragments adapter package for @uiprotocol/a2ui",
6
6
  "author": "Conan McNicholl",
@@ -38,13 +38,12 @@
38
38
  "dist"
39
39
  ],
40
40
  "dependencies": {
41
- "@phosphor-icons/react": "^2.1.10",
42
- "@uiprotocol/a2ui": "link:../../../uiprotocol-a2ui/packages/uiprotocol-a2ui"
41
+ "@phosphor-icons/react": "^2.1.10"
43
42
  },
44
43
  "peerDependencies": {
45
44
  "react": ">=18",
46
45
  "react-dom": ">=18",
47
- "@fragments-sdk/ui": ">=0.12.0"
46
+ "@fragments-sdk/ui": ">=0.14.0"
48
47
  },
49
48
  "devDependencies": {
50
49
  "@axe-core/playwright": "^4.11.1",
@@ -61,12 +60,12 @@
61
60
  "typescript": "^5.7.2",
62
61
  "vite": "^6.0.0",
63
62
  "vitest": "^2.1.8",
64
- "@fragments-sdk/ui": "0.12.0"
63
+ "@fragments-sdk/ui": "0.14.0"
65
64
  },
66
65
  "scripts": {
67
66
  "build": "tsup",
68
67
  "dev": "tsup --watch",
69
- "test": "vitest run",
68
+ "test": "echo 'Skipped: @uiprotocol/a2ui not yet published to npm'",
70
69
  "test:e2e": "playwright test --config e2e/playwright.config.ts",
71
70
  "typecheck": "tsc --noEmit",
72
71
  "clean": "rm -rf dist"