@vertebr/react 0.2.0 → 0.3.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.
Files changed (59) hide show
  1. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
  2. package/dist/components/admin/VertebrAdmin.js +25 -3
  3. package/dist/components/config/ConfigForm.d.ts +2 -18
  4. package/dist/components/config/ConfigForm.d.ts.map +1 -1
  5. package/dist/components/config/ConfigForm.js +1 -281
  6. package/dist/components/config/DescriptionInput.d.ts +18 -0
  7. package/dist/components/config/DescriptionInput.d.ts.map +1 -0
  8. package/dist/components/config/DescriptionInput.js +152 -0
  9. package/dist/components/config/ImagePickerModal.d.ts +18 -0
  10. package/dist/components/config/ImagePickerModal.d.ts.map +1 -0
  11. package/dist/components/config/ImagePickerModal.js +120 -0
  12. package/dist/components/config/PriceInput.d.ts +12 -0
  13. package/dist/components/config/PriceInput.d.ts.map +1 -0
  14. package/dist/components/config/PriceInput.js +20 -0
  15. package/dist/components/config/form/ConfigField.d.ts +21 -0
  16. package/dist/components/config/form/ConfigField.d.ts.map +1 -0
  17. package/dist/components/config/form/ConfigField.js +48 -0
  18. package/dist/components/config/form/ConfigForm.d.ts +4 -0
  19. package/dist/components/config/form/ConfigForm.d.ts.map +1 -0
  20. package/dist/components/config/form/ConfigForm.js +70 -0
  21. package/dist/components/config/form/ConfigFormActions.d.ts +10 -0
  22. package/dist/components/config/form/ConfigFormActions.d.ts.map +1 -0
  23. package/dist/components/config/form/ConfigFormActions.js +4 -0
  24. package/dist/components/config/form/RecordConfigForm.d.ts +21 -0
  25. package/dist/components/config/form/RecordConfigForm.d.ts.map +1 -0
  26. package/dist/components/config/form/RecordConfigForm.js +37 -0
  27. package/dist/components/config/form/SettingsConfigForm.d.ts +21 -0
  28. package/dist/components/config/form/SettingsConfigForm.d.ts.map +1 -0
  29. package/dist/components/config/form/SettingsConfigForm.js +7 -0
  30. package/dist/components/config/form/helpers.d.ts +16 -0
  31. package/dist/components/config/form/helpers.d.ts.map +1 -0
  32. package/dist/components/config/form/helpers.js +186 -0
  33. package/dist/components/config/form/types.d.ts +27 -0
  34. package/dist/components/config/form/types.d.ts.map +1 -0
  35. package/dist/components/config/form/types.js +1 -0
  36. package/dist/components/resources/ResourceCell.d.ts.map +1 -1
  37. package/dist/components/resources/ResourceCell.js +5 -19
  38. package/dist/components/resources/ResourceTable.d.ts +5 -2
  39. package/dist/components/resources/ResourceTable.d.ts.map +1 -1
  40. package/dist/components/resources/ResourceTable.js +54 -5
  41. package/dist/hooks/useVertebrControls.d.ts.map +1 -1
  42. package/dist/hooks/useVertebrControls.js +5 -4
  43. package/dist/index.d.ts +7 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +4 -1
  46. package/dist/lib/description.d.ts +18 -0
  47. package/dist/lib/description.d.ts.map +1 -0
  48. package/dist/lib/description.js +85 -0
  49. package/dist/lib/images.d.ts +14 -0
  50. package/dist/lib/images.d.ts.map +1 -0
  51. package/dist/lib/images.js +63 -0
  52. package/dist/lib/price.d.ts +15 -0
  53. package/dist/lib/price.d.ts.map +1 -0
  54. package/dist/lib/price.js +50 -0
  55. package/dist/styles/form.css +275 -0
  56. package/dist/styles/modal.css +115 -0
  57. package/dist/styles/panel.css +12 -1
  58. package/dist/styles/table.css +10 -1
  59. package/package.json +9 -2
@@ -0,0 +1,186 @@
1
+ import { z } from "zod";
2
+ import { isDescriptionField } from "../../../lib/description";
3
+ import { isImageField, looksLikeUrl } from "../../../lib/images";
4
+ const RECORD_GROUP_ORDER = [
5
+ "Details",
6
+ "Content",
7
+ "Pricing",
8
+ "Inventory",
9
+ "Media",
10
+ "Status",
11
+ ];
12
+ export function buildZodSchema(schema) {
13
+ const shape = {};
14
+ for (const [key, field] of Object.entries(schema)) {
15
+ let zodField;
16
+ switch (field.type) {
17
+ case "number":
18
+ zodField = z.number();
19
+ if (field.min != null)
20
+ zodField = zodField.min(field.min);
21
+ if (field.max != null)
22
+ zodField = zodField.max(field.max);
23
+ break;
24
+ case "boolean":
25
+ zodField = z.boolean();
26
+ break;
27
+ case "file[]":
28
+ zodField = z.array(z.string());
29
+ break;
30
+ default:
31
+ zodField = z.string();
32
+ }
33
+ if (!field.required) {
34
+ zodField = zodField.optional();
35
+ }
36
+ shape[key] = zodField;
37
+ }
38
+ return z.object(shape);
39
+ }
40
+ export function humanizeKey(key) {
41
+ return key
42
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
43
+ .replace(/[_-]+/g, " ")
44
+ .trim();
45
+ }
46
+ function escapeRegExp(value) {
47
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
48
+ }
49
+ export function isStockField(key, field) {
50
+ const hay = `${key} ${field?.label ?? ""}`;
51
+ return /(stock|inventory|qty|quantity)/i.test(hay);
52
+ }
53
+ export function spanFor(key, field, variant) {
54
+ if (field.type === "boolean")
55
+ return "full";
56
+ if (field.type === "richtext" || isDescriptionField(key, field))
57
+ return "full";
58
+ if (variant === "record" && isImageField(key, field))
59
+ return "full";
60
+ return undefined;
61
+ }
62
+ function inferConfigGroup(key, field) {
63
+ if (field.group?.trim())
64
+ return field.group.trim();
65
+ const source = field.label?.trim() || humanizeKey(key);
66
+ const words = source.split(/\s+/).filter(Boolean);
67
+ if (words.length >= 2)
68
+ return words[0];
69
+ return "General";
70
+ }
71
+ function inferRecordGroup(key, field) {
72
+ if (field.group?.trim())
73
+ return field.group.trim();
74
+ const k = key.toLowerCase();
75
+ const label = (field.label ?? "").toLowerCase();
76
+ const hay = `${k} ${label}`;
77
+ if (/(image|img|photo|avatar|thumbnail|thumb|cover|media_url)/i.test(hay)) {
78
+ return "Media";
79
+ }
80
+ if (/(price|cost|cents|amount|msrp)/i.test(hay)) {
81
+ return "Pricing";
82
+ }
83
+ if (/(stock|inventory|qty|quantity)/i.test(hay)) {
84
+ return "Inventory";
85
+ }
86
+ if (field.type === "boolean" ||
87
+ /(^|_)(active|enabled|published|status|visible)(_|$)/i.test(k) ||
88
+ /^(active|enabled|published|status|visible)$/i.test(label)) {
89
+ return "Status";
90
+ }
91
+ if (field.type === "richtext" ||
92
+ /(description|body|content|summary|bio)/i.test(hay)) {
93
+ return "Content";
94
+ }
95
+ return "Details";
96
+ }
97
+ export function displayLabel(key, field, group) {
98
+ const label = field.label?.trim() || humanizeKey(key);
99
+ const stripped = label.replace(new RegExp(`^${escapeRegExp(group)}\\s+`, "i"), "");
100
+ if (/^inventory$/i.test(group)) {
101
+ const stock = label.replace(/^stock\s+/i, "").trim();
102
+ if (stock && stock !== label)
103
+ return stock;
104
+ }
105
+ if (/^pricing$/i.test(group) && /cents/i.test(label)) {
106
+ return label.replace(/\s*\(cents\)/i, "").trim() || label;
107
+ }
108
+ return stripped || label;
109
+ }
110
+ function sortGroups(groups, variant) {
111
+ if (variant !== "record")
112
+ return groups;
113
+ const rank = (id) => {
114
+ const index = RECORD_GROUP_ORDER.indexOf(id);
115
+ return index === -1 ? RECORD_GROUP_ORDER.length + 1 : index;
116
+ };
117
+ return [...groups].sort((a, b) => rank(a.id) - rank(b.id));
118
+ }
119
+ export function groupFields(schema, variant) {
120
+ const order = [];
121
+ const map = new Map();
122
+ const explicit = new Set();
123
+ for (const entry of Object.entries(schema)) {
124
+ const [key, field] = entry;
125
+ const group = variant === "record"
126
+ ? inferRecordGroup(key, field)
127
+ : inferConfigGroup(key, field);
128
+ if (field.group?.trim())
129
+ explicit.add(group);
130
+ if (!map.has(group)) {
131
+ map.set(group, []);
132
+ order.push(group);
133
+ }
134
+ map.get(group).push(entry);
135
+ }
136
+ if (variant === "config") {
137
+ for (const [group, fields] of [...map.entries()]) {
138
+ if (fields.length > 1 || explicit.has(group) || group === "General") {
139
+ continue;
140
+ }
141
+ if (!map.has("General")) {
142
+ map.set("General", []);
143
+ order.unshift("General");
144
+ }
145
+ map.get("General").push(...fields);
146
+ map.delete(group);
147
+ const idx = order.indexOf(group);
148
+ if (idx >= 0)
149
+ order.splice(idx, 1);
150
+ }
151
+ }
152
+ const groups = order
153
+ .filter((id) => map.has(id))
154
+ .map((id) => ({
155
+ id,
156
+ label: id,
157
+ fields: map.get(id),
158
+ }));
159
+ return sortGroups(groups, variant);
160
+ }
161
+ export function firstImageUrl(value) {
162
+ const items = Array.isArray(value) ? value : [value];
163
+ for (const item of items) {
164
+ if (typeof item === "string" && looksLikeUrl(item))
165
+ return item;
166
+ if (item && typeof item === "object" && "url" in item) {
167
+ const url = item.url;
168
+ if (typeof url === "string" && looksLikeUrl(url))
169
+ return url;
170
+ }
171
+ }
172
+ return null;
173
+ }
174
+ export function findPrimaryImageKey(schema, values) {
175
+ for (const [key, field] of Object.entries(schema)) {
176
+ if (!isImageField(key, field))
177
+ continue;
178
+ if (firstImageUrl(values?.[key]))
179
+ return key;
180
+ }
181
+ for (const [key, field] of Object.entries(schema)) {
182
+ if (isImageField(key, field))
183
+ return key;
184
+ }
185
+ return null;
186
+ }
@@ -0,0 +1,27 @@
1
+ import type { FieldSchema, GalleryItem, ResourceUploadMeta } from "@vertebr/client";
2
+ export type ConfigFormVariant = "config" | "record";
3
+ export type ConfigFormProps = {
4
+ schema: Record<string, FieldSchema>;
5
+ value: Record<string, unknown>;
6
+ onSave: (value: Record<string, unknown>) => Promise<void>;
7
+ /** `record` uses a dense product-style editor. */
8
+ variant?: ConfigFormVariant;
9
+ /** Field-keyed upload capabilities from the host resource. */
10
+ uploads?: Record<string, ResourceUploadMeta>;
11
+ /** Upload files for a field; return public URL(s). */
12
+ onUpload?: (field: string, files: File[]) => Promise<Array<{
13
+ url: string;
14
+ name?: string;
15
+ }>>;
16
+ galleryUpload?: Pick<ResourceUploadMeta, "accept" | "maxBytes">;
17
+ onListGallery?: () => Promise<GalleryItem[]>;
18
+ onUploadGallery?: (files: File[]) => Promise<GalleryItem[]>;
19
+ };
20
+ export type FieldEntry = [string, FieldSchema];
21
+ export type FieldGroup = {
22
+ id: string;
23
+ label: string;
24
+ fields: FieldEntry[];
25
+ };
26
+ export type GalleryHandlers = Pick<ConfigFormProps, "galleryUpload" | "onListGallery" | "onUploadGallery">;
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEpD,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,kDAAkD;IAClD,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,sDAAsD;IACtD,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACpD,aAAa,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAE/C,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,eAAe,EACf,eAAe,GAAG,eAAe,GAAG,iBAAiB,CACtD,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1 @@
1
- {"version":3,"file":"ResourceCell.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceCell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAuDnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC9B,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,KAAK,EACL,KAAK,EACL,OAAiB,GAClB,EAAE,iBAAiB,+BAsInB"}
1
+ {"version":3,"file":"ResourceCell.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceCell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA6CnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC9B,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,KAAK,EACL,KAAK,EACL,OAAiB,GAClB,EAAE,iBAAiB,+BA0InB"}
@@ -1,8 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { formatPrice, isCentsField, isPriceField, } from "../../lib/price";
2
3
  const IMAGE_KEY = /(image|img|photo|avatar|thumbnail|thumb|cover)/i;
3
4
  const URL_KEY = /(url|href|link|src)/i;
4
- const CENTS_KEY = /(cents|priceCents|amountCents)/i;
5
- const PRICE_KEY = /(price|amount|cost|total)/i;
6
5
  const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|avif)(\?|#|$)/i;
7
6
  function looksLikeUrl(value) {
8
7
  return /^https?:\/\//i.test(value) || value.startsWith("//");
@@ -37,19 +36,6 @@ function formatDate(value) {
37
36
  }
38
37
  return null;
39
38
  }
40
- function formatMoney(value, asCents) {
41
- const amount = asCents ? value / 100 : value;
42
- try {
43
- return new Intl.NumberFormat(undefined, {
44
- style: "currency",
45
- currency: "USD",
46
- maximumFractionDigits: asCents || !Number.isInteger(amount) ? 2 : 0,
47
- }).format(amount);
48
- }
49
- catch {
50
- return asCents ? `$${(value / 100).toFixed(2)}` : String(value);
51
- }
52
- }
53
39
  export function ResourceCell({ fieldKey, field, value, variant = "table", }) {
54
40
  const detail = variant === "detail";
55
41
  if (value == null || value === "") {
@@ -68,11 +54,11 @@ export function ResourceCell({ fieldKey, field, value, variant = "table", }) {
68
54
  }
69
55
  if (type === "number" || typeof value === "number") {
70
56
  if (typeof value === "number" && Number.isFinite(value)) {
71
- if (CENTS_KEY.test(fieldKey) || /cents/i.test(field?.label ?? "")) {
72
- return (_jsx("span", { className: "vertebr-cell-money", children: formatMoney(value, true) }));
57
+ if (isCentsField(fieldKey, field)) {
58
+ return (_jsx("span", { className: "vertebr-cell-money", children: formatPrice(value, { asCents: true }) }));
73
59
  }
74
- if (PRICE_KEY.test(fieldKey)) {
75
- return (_jsx("span", { className: "vertebr-cell-money", children: formatMoney(value, false) }));
60
+ if (isPriceField(fieldKey, field)) {
61
+ return (_jsx("span", { className: "vertebr-cell-money", children: formatPrice(value, { asCents: false }) }));
76
62
  }
77
63
  return (_jsx("span", { className: "vertebr-cell-number", children: new Intl.NumberFormat().format(value) }));
78
64
  }
@@ -1,4 +1,4 @@
1
- import type { FieldSchema, ResourceActionMeta, ResourceUploadMeta } from "@vertebr/client";
1
+ import type { FieldSchema, GalleryItem, ResourceActionMeta, ResourceUploadMeta } from "@vertebr/client";
2
2
  type ResourcePanelProps = {
3
3
  schema: Record<string, FieldSchema>;
4
4
  items: Record<string, unknown>[];
@@ -20,8 +20,11 @@ type ResourcePanelProps = {
20
20
  url: string;
21
21
  name?: string;
22
22
  }>>;
23
+ galleryUpload?: Pick<ResourceUploadMeta, "accept" | "maxBytes">;
24
+ onListGallery?: () => Promise<GalleryItem[]>;
25
+ onUploadGallery?: (files: File[]) => Promise<GalleryItem[]>;
23
26
  };
24
- export declare function ResourcePanel({ schema, items, permissions, actions, uploads, rowIdKeys, storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, }: ResourcePanelProps): import("react").JSX.Element;
27
+ export declare function ResourcePanel({ schema, items, permissions, actions, uploads, rowIdKeys, storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }: ResourcePanelProps): import("react").JSX.Element;
25
28
  /** @deprecated Prefer ResourcePanel */
26
29
  export declare function ResourceTable(props: {
27
30
  schema: Record<string, FieldSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"ResourceTable.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceTable.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AAMzB,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACjC,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACrD,CAAC;AA+DF,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAY,EACZ,OAAO,EACP,SAA0C,EAC1C,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,GACT,EAAE,kBAAkB,+BAoVpB;AAED,uCAAuC;AACvC,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CAClC,+BAEA"}
1
+ {"version":3,"file":"ResourceTable.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceTable.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AAMzB,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACjC,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACpD,aAAa,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC7D,CAAC;AA+DF,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAY,EACZ,OAAO,EACP,SAA0C,EAC1C,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,GAChB,EAAE,kBAAkB,+BA+ZpB;AAED,uCAAuC;AACvC,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CAClC,+BAEA"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useState } from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table";
4
4
  import { ConfigForm } from "../config/ConfigForm";
5
5
  import { BulkImageUploadModal } from "./BulkImageUploadModal";
@@ -64,11 +64,17 @@ function titleFromFilename(name) {
64
64
  .replace(/\s+/g, " ")
65
65
  .trim();
66
66
  }
67
- export function ResourcePanel({ schema, items, permissions, actions = {}, uploads, rowIdKeys = ["id", "slug", "uid", "email"], storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, }) {
67
+ export function ResourcePanel({ schema, items, permissions, actions = {}, uploads, rowIdKeys = ["id", "slug", "uid", "email"], storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }) {
68
68
  const [editing, setEditing] = useState(null);
69
69
  const [viewing, setViewing] = useState(null);
70
70
  const [bulkOpen, setBulkOpen] = useState(false);
71
71
  const [busyAction, setBusyAction] = useState(null);
72
+ const tableWrapRef = useRef(null);
73
+ const [tableOverflow, setTableOverflow] = useState({
74
+ overflow: false,
75
+ canLeft: false,
76
+ canRight: false,
77
+ });
72
78
  const [columnVisibility, setColumnVisibility] = useState(() => readVisibility(storageKey));
73
79
  const canUpdate = Boolean(permissions?.update && onUpdate);
74
80
  const canCreate = Boolean(permissions?.create && onCreate);
@@ -82,6 +88,43 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
82
88
  useEffect(() => {
83
89
  writeVisibility(storageKey, columnVisibility);
84
90
  }, [storageKey, columnVisibility]);
91
+ const updateTableOverflow = useCallback(() => {
92
+ const el = tableWrapRef.current;
93
+ if (!el) {
94
+ setTableOverflow({ overflow: false, canLeft: false, canRight: false });
95
+ return;
96
+ }
97
+ const overflow = el.scrollWidth > el.clientWidth + 2;
98
+ setTableOverflow({
99
+ overflow,
100
+ canLeft: overflow && el.scrollLeft > 2,
101
+ canRight: overflow && el.scrollLeft + el.clientWidth < el.scrollWidth - 2,
102
+ });
103
+ }, []);
104
+ useEffect(() => {
105
+ const el = tableWrapRef.current;
106
+ if (!el)
107
+ return;
108
+ updateTableOverflow();
109
+ const onScroll = () => updateTableOverflow();
110
+ el.addEventListener("scroll", onScroll, { passive: true });
111
+ const observer = new ResizeObserver(updateTableOverflow);
112
+ observer.observe(el);
113
+ return () => {
114
+ el.removeEventListener("scroll", onScroll);
115
+ observer.disconnect();
116
+ };
117
+ }, [updateTableOverflow, items.length, columnVisibility]);
118
+ const scrollTable = (direction) => {
119
+ const el = tableWrapRef.current;
120
+ if (!el)
121
+ return;
122
+ const amount = Math.max(220, Math.round(el.clientWidth * 0.55));
123
+ el.scrollBy({
124
+ left: direction === "left" ? -amount : amount,
125
+ behavior: "smooth",
126
+ });
127
+ };
85
128
  const columnOptions = useMemo(() => fieldKeys.map((key) => ({
86
129
  id: key,
87
130
  label: schema[key]?.label ?? humanizeKey(key),
@@ -151,12 +194,12 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
151
194
  if (editing && editingId && onUpdate) {
152
195
  const editableSchema = Object.fromEntries(Object.entries(schema).filter(([, field]) => !field.readOnly));
153
196
  const title = rowTitle(editing, rowIdKeys);
154
- return (_jsxs("div", { className: "vertebr-resource-edit", children: [_jsxs("div", { className: "vertebr-resource-edit-head", children: [_jsxs("div", { className: "vertebr-resource-edit-heading", children: [_jsx("p", { className: "vertebr-resource-edit-eyebrow", children: "Editing record" }), _jsx("h2", { children: title }), _jsx("p", { className: "vertebr-resource-edit-id", children: editingId })] }), _jsx("button", { type: "button", className: "vertebr-btn", onClick: () => setEditing(null), children: "Back" })] }), _jsx(ConfigForm, { variant: "record", schema: editableSchema, value: editing, uploads: uploads, onUpload: onUpload, onSave: async (next) => {
197
+ return (_jsxs("div", { className: "vertebr-resource-edit", children: [_jsxs("div", { className: "vertebr-resource-edit-head", children: [_jsxs("div", { className: "vertebr-resource-edit-heading", children: [_jsx("p", { className: "vertebr-resource-edit-eyebrow", children: "Editing record" }), _jsx("h2", { children: title }), _jsx("p", { className: "vertebr-resource-edit-id", children: editingId })] }), _jsx("button", { type: "button", className: "vertebr-btn", onClick: () => setEditing(null), children: "Back" })] }), _jsx(ConfigForm, { variant: "record", schema: editableSchema, value: editing, uploads: uploads, onUpload: onUpload, galleryUpload: galleryUpload, onListGallery: onListGallery, onUploadGallery: onUploadGallery, onSave: async (next) => {
155
198
  await onUpdate(editingId, next);
156
199
  setEditing(null);
157
200
  } })] }));
158
201
  }
159
- const toolbar = (_jsx("div", { className: "vertebr-table-toolbar", children: _jsxs("div", { className: "vertebr-table-toolbar-start", children: [columnOptions.length > 0 ? (_jsx(ColumnVisibilityMenu, { columns: columnOptions, visibility: columnVisibility, onChange: setColumnVisibility })) : null, canUpload ? (_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon", onClick: () => setBulkOpen(true), children: [_jsx(ImageUploadIcon, {}), "Bulk upload"] })) : null] }) }));
202
+ const toolbar = (_jsxs("div", { className: "vertebr-table-toolbar", children: [_jsxs("div", { className: "vertebr-table-toolbar-start", children: [columnOptions.length > 0 ? (_jsx(ColumnVisibilityMenu, { columns: columnOptions, visibility: columnVisibility, onChange: setColumnVisibility })) : null, canUpload ? (_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon", onClick: () => setBulkOpen(true), children: [_jsx(ImageUploadIcon, {}), "Bulk upload"] })) : null] }), tableOverflow.overflow ? (_jsxs("div", { className: "vertebr-table-scroll-controls", role: "group", "aria-label": "Scroll columns", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canLeft, onClick: () => scrollTable("left"), "aria-label": "Scroll columns left", children: _jsx(ChevronLeftIcon, {}) }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canRight, onClick: () => scrollTable("right"), "aria-label": "Scroll columns right", children: _jsx(ChevronRightIcon, {}) })] })) : null] }));
160
203
  const bulkModal = bulkOpen && uploads && onUpload ? (_jsx(BulkImageUploadModal, { uploads: uploads, canCreate: canCreate, fieldLabels: Object.fromEntries(Object.keys(uploads).map((key) => [
161
204
  key,
162
205
  schema[key]?.label ?? humanizeKey(key),
@@ -175,7 +218,7 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
175
218
  if (items.length === 0) {
176
219
  return (_jsxs("div", { className: "vertebr-resource-body", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Records" }), _jsx("span", { className: "vertebr-count", children: "0" })] }), canUpload || columnOptions.length > 0 ? toolbar : null, _jsx("div", { className: "vertebr-empty", children: "No rows yet." }), bulkModal] }));
177
220
  }
178
- return (_jsxs("div", { className: "vertebr-resource-body", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Records" }), _jsx("span", { className: "vertebr-count", children: items.length })] }), canUpload || columnOptions.length > 0 ? toolbar : null, _jsx("div", { className: "vertebr-table-wrap", children: _jsxs("table", { className: "vertebr-table", children: [_jsx("thead", { children: table.getHeaderGroups().map((headerGroup) => (_jsx("tr", { children: headerGroup.headers.map((header) => (_jsx("th", { className: header.column.id === "_actions"
221
+ return (_jsxs("div", { className: "vertebr-resource-body", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Records" }), _jsx("span", { className: "vertebr-count", children: items.length })] }), toolbar, _jsx("div", { className: "vertebr-table-wrap", ref: tableWrapRef, "data-overflow": tableOverflow.overflow ? "true" : undefined, "data-overflow-left": tableOverflow.canLeft ? "true" : undefined, "data-overflow-right": tableOverflow.canRight ? "true" : undefined, children: _jsxs("table", { className: "vertebr-table", children: [_jsx("thead", { children: table.getHeaderGroups().map((headerGroup) => (_jsx("tr", { children: headerGroup.headers.map((header) => (_jsx("th", { className: header.column.id === "_actions"
179
222
  ? "vertebr-col-actions"
180
223
  : undefined, children: header.isPlaceholder
181
224
  ? null
@@ -195,6 +238,12 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
195
238
  export function ResourceTable(props) {
196
239
  return _jsx(ResourcePanel, { ...props });
197
240
  }
241
+ function ChevronLeftIcon() {
242
+ return (_jsx("svg", { className: "vertebr-btn-glyph", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m15 18-6-6 6-6" }) }));
243
+ }
244
+ function ChevronRightIcon() {
245
+ return (_jsx("svg", { className: "vertebr-btn-glyph", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m9 18 6-6-6-6" }) }));
246
+ }
198
247
  function ImageUploadIcon() {
199
248
  return (_jsxs("svg", { className: "vertebr-btn-glyph", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M16 5h6" }), _jsx("path", { d: "M19 2v6" }), _jsx("rect", { x: "3", y: "7", width: "14", height: "14", rx: "2" }), _jsx("circle", { cx: "8.5", cy: "12.5", r: "1.5" }), _jsx("path", { d: "m17 21-4.5-4.5L7 21" })] }));
200
249
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useVertebrControls.d.ts","sourceRoot":"","sources":["../../src/hooks/useVertebrControls.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,KAAK,oBAAoB,GAAG;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAIF,MAAM,MAAM,4BAA4B,GAAG;IACzC,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAEnC;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,OAAsB,EACtB,WAAW,EACX,QAAQ,GACT,EAAE,4BAA4B,+BAuC9B;AAYD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChE,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,CAAC,GACV;IACD,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAMA;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,oBAAoB,CAEzD"}
1
+ {"version":3,"file":"useVertebrControls.d.ts","sourceRoot":"","sources":["../../src/hooks/useVertebrControls.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,KAAK,oBAAoB,GAAG;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAIF,MAAM,MAAM,4BAA4B,GAAG;IACzC,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAEnC;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,OAAsB,EACtB,WAAW,EACX,QAAQ,GACT,EAAE,4BAA4B,+BAyC9B;AAYD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChE,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,CAAC,GACV;IACD,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAMA;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,oBAAoB,CAEzD"}
@@ -13,11 +13,11 @@ export function VertebrControlsProvider({ apiBase = "/admin/api", environment, c
13
13
  apiBase,
14
14
  getEnvironment: () => environment,
15
15
  }), [apiBase, environment]);
16
- const refresh = useCallback(async () => {
16
+ const load = useCallback(async (opts) => {
17
17
  setLoading(true);
18
18
  setError(null);
19
19
  try {
20
- const next = await client.getPublicControls();
20
+ const next = await client.getPublicControls(opts);
21
21
  setControls(next);
22
22
  }
23
23
  catch (err) {
@@ -27,9 +27,10 @@ export function VertebrControlsProvider({ apiBase = "/admin/api", environment, c
27
27
  setLoading(false);
28
28
  }
29
29
  }, [client]);
30
+ const refresh = useCallback(() => load({ fresh: true }), [load]);
30
31
  useEffect(() => {
31
- void refresh();
32
- }, [refresh]);
32
+ void load();
33
+ }, [load]);
33
34
  const value = useMemo(() => ({ environment, controls, loading, error, refresh }), [environment, controls, loading, error, refresh]);
34
35
  return (_jsx(ControlsContext.Provider, { value: value, children: children }));
35
36
  }
package/dist/index.d.ts CHANGED
@@ -6,11 +6,16 @@ export { ResourcePanel, ResourceTable } from "./components/resources/ResourceTab
6
6
  export { ThemeToggle } from "./components/theme/ThemeToggle";
7
7
  export type { ThemeToggleProps } from "./components/theme/ThemeToggle";
8
8
  export { ConfigForm } from "./components/config/ConfigForm";
9
+ export { formatPrice, formatPriceInput, parsePriceInput, isCentsField, isPriceField, } from "./lib/price";
10
+ export { isDescriptionField, descriptionStats, applyMarkdown, } from "./lib/description";
11
+ export type { MarkdownCommand, MarkdownEdit } from "./lib/description";
9
12
  export { EnvSelect } from "./components/env/EnvSelect";
10
13
  export { useVertebrTheme } from "./hooks/useVertebrTheme";
11
14
  export type { VbTheme } from "./hooks/useVertebrTheme";
12
15
  export { VertebrControlsProvider, useVertebrConfig, useVertebrControls, } from "./hooks/useVertebrControls";
13
16
  export type { VertebrControlsProviderProps } from "./hooks/useVertebrControls";
14
- export { createVertebrClient, VERTEBR_ENV_HEADER, } from "@vertebr/client";
15
- export type { VertebrClient, VertebrClientOptions, AdminEnvironment, BootstrapResponse, CatalogResponse, CatalogResource, CatalogConfig, CatalogConfigSection, EnvironmentAuthPublic, FieldSchema, FieldType, GalleryItem, GalleryMeta, GalleryResponse, MetricCard, MetricsResponse, ResourceListResponse, ResourceActionMeta, ResourceUploadMeta, ResourceUploadResponse, ResourceBulkUploadResponse, ConfigResponse, VertebrControlsMap, } from "@vertebr/client";
17
+ export { createVertebrClient, VERTEBR_ENV_HEADER, renderMarkdown, } from "@vertebr/client";
18
+ export { VertebrMarkdown } from "@vertebr/client/react";
19
+ export type { VertebrMarkdownProps } from "@vertebr/client/react";
20
+ export type { VertebrClient, VertebrClientOptions, VertebrReadOptions, AdminEnvironment, BootstrapResponse, CatalogResponse, CatalogResource, CatalogConfig, CatalogConfigSection, EnvironmentAuthPublic, FieldSchema, FieldType, GalleryItem, GalleryMeta, GalleryResponse, MetricCard, MetricsResponse, ResourceListResponse, ResourceActionMeta, ResourceUploadMeta, ResourceUploadResponse, ResourceBulkUploadResponse, ConfigResponse, VertebrControlsMap, } from "@vertebr/client";
16
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EACL,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -4,8 +4,11 @@ export { GalleryBoard } from "./components/gallery/GalleryBoard";
4
4
  export { ResourcePanel, ResourceTable } from "./components/resources/ResourceTable";
5
5
  export { ThemeToggle } from "./components/theme/ThemeToggle";
6
6
  export { ConfigForm } from "./components/config/ConfigForm";
7
+ export { formatPrice, formatPriceInput, parsePriceInput, isCentsField, isPriceField, } from "./lib/price";
8
+ export { isDescriptionField, descriptionStats, applyMarkdown, } from "./lib/description";
7
9
  export { EnvSelect } from "./components/env/EnvSelect";
8
10
  export { useVertebrTheme } from "./hooks/useVertebrTheme";
9
11
  export { VertebrControlsProvider, useVertebrConfig, useVertebrControls, } from "./hooks/useVertebrControls";
10
12
  // Re-export the framework-agnostic client for convenience.
11
- export { createVertebrClient, VERTEBR_ENV_HEADER, } from "@vertebr/client";
13
+ export { createVertebrClient, VERTEBR_ENV_HEADER, renderMarkdown, } from "@vertebr/client";
14
+ export { VertebrMarkdown } from "@vertebr/client/react";
@@ -0,0 +1,18 @@
1
+ import type { FieldSchema } from "@vertebr/client";
2
+ export declare function isDescriptionField(key: string, field?: Pick<FieldSchema, "label" | "type">): boolean;
3
+ export declare function descriptionStats(value: unknown): {
4
+ characters: number;
5
+ words: number;
6
+ };
7
+ export type MarkdownCommand = "bold" | "italic" | "heading" | "ul" | "ol" | "link";
8
+ export type MarkdownEdit = {
9
+ next: string;
10
+ inserted: string;
11
+ replaceStart: number;
12
+ replaceEnd: number;
13
+ selectionStart: number;
14
+ selectionEnd: number;
15
+ };
16
+ /** Apply a markdown format command around the current selection. */
17
+ export declare function applyMarkdown(text: string, start: number, end: number, command: MarkdownCommand): MarkdownEdit;
18
+ //# sourceMappingURL=description.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"description.d.ts","sourceRoot":"","sources":["../../src/lib/description.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAKnD,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,MAAM,CAAC,GAC1C,OAAO,CAIT;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf,CAOA;AAED,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,QAAQ,GACR,SAAS,GACT,IAAI,GACJ,IAAI,GACJ,MAAM,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AA8CF,oEAAoE;AACpE,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,eAAe,GACvB,YAAY,CAsCd"}
@@ -0,0 +1,85 @@
1
+ const DESCRIPTION_KEY = /(description|body|content|summary|bio|copy|caption|details)/i;
2
+ export function isDescriptionField(key, field) {
3
+ if (field?.type === "richtext")
4
+ return true;
5
+ const hay = `${key} ${field?.label ?? ""}`;
6
+ return DESCRIPTION_KEY.test(hay);
7
+ }
8
+ export function descriptionStats(value) {
9
+ const text = typeof value === "string" ? value.trim() : "";
10
+ if (!text)
11
+ return { characters: 0, words: 0 };
12
+ return {
13
+ characters: text.length,
14
+ words: text.split(/\s+/).filter(Boolean).length,
15
+ };
16
+ }
17
+ function wrap(text, start, end, before, after, placeholder) {
18
+ const selected = text.slice(start, end) || placeholder;
19
+ const inserted = before + selected + after;
20
+ return {
21
+ next: text.slice(0, start) + inserted + text.slice(end),
22
+ inserted,
23
+ replaceStart: start,
24
+ replaceEnd: end,
25
+ selectionStart: start + before.length,
26
+ selectionEnd: start + before.length + selected.length,
27
+ };
28
+ }
29
+ function prefixLines(text, start, end, prefixFor) {
30
+ const lineStart = text.lastIndexOf("\n", Math.max(0, start - 1)) + 1;
31
+ const lineEndIndex = text.indexOf("\n", end);
32
+ const lineEnd = lineEndIndex === -1 ? text.length : lineEndIndex;
33
+ const block = text.slice(lineStart, lineEnd) || "";
34
+ const lines = block.split("\n");
35
+ const inserted = lines
36
+ .map((line, index) => prefixFor(index, line))
37
+ .join("\n");
38
+ return {
39
+ next: text.slice(0, lineStart) + inserted + text.slice(lineEnd),
40
+ inserted,
41
+ replaceStart: lineStart,
42
+ replaceEnd: lineEnd,
43
+ selectionStart: lineStart,
44
+ selectionEnd: lineStart + inserted.length,
45
+ };
46
+ }
47
+ /** Apply a markdown format command around the current selection. */
48
+ export function applyMarkdown(text, start, end, command) {
49
+ const from = Math.max(0, Math.min(start, end, text.length));
50
+ const to = Math.max(0, Math.min(Math.max(start, end), text.length));
51
+ switch (command) {
52
+ case "bold":
53
+ return wrap(text, from, to, "**", "**", "bold text");
54
+ case "italic":
55
+ return wrap(text, from, to, "*", "*", "italic text");
56
+ case "link": {
57
+ const selected = text.slice(from, to) || "link text";
58
+ const inserted = `[${selected}](https://)`;
59
+ const urlStart = from + selected.length + 3;
60
+ return {
61
+ next: text.slice(0, from) + inserted + text.slice(to),
62
+ inserted,
63
+ replaceStart: from,
64
+ replaceEnd: to,
65
+ selectionStart: urlStart,
66
+ selectionEnd: urlStart + "https://".length,
67
+ };
68
+ }
69
+ case "heading":
70
+ return prefixLines(text, from, to, (_index, line) => {
71
+ const stripped = line.replace(/^#{1,6}\s+/, "");
72
+ return `## ${stripped || "Heading"}`;
73
+ });
74
+ case "ul":
75
+ return prefixLines(text, from, to, (_index, line) => {
76
+ const stripped = line.replace(/^[-*]\s+/, "").replace(/^\d+\.\s+/, "");
77
+ return `- ${stripped || "List item"}`;
78
+ });
79
+ case "ol":
80
+ return prefixLines(text, from, to, (index, line) => {
81
+ const stripped = line.replace(/^[-*]\s+/, "").replace(/^\d+\.\s+/, "");
82
+ return `${index + 1}. ${stripped || "List item"}`;
83
+ });
84
+ }
85
+ }
@@ -0,0 +1,14 @@
1
+ import type { FieldSchema } from "@vertebr/client";
2
+ export type RecordImage = {
3
+ id: string;
4
+ url: string;
5
+ name: string;
6
+ field: string;
7
+ };
8
+ declare function looksLikeUrl(value: string): boolean;
9
+ export declare function isImageField(key: string, field?: Pick<FieldSchema, "label" | "type">): boolean;
10
+ /** Flatten record image fields (single URL or URL lists) for the editor picker. */
11
+ export declare function collectRecordImages(schema: Record<string, FieldSchema>, values: Record<string, unknown> | undefined): RecordImage[];
12
+ export declare function urlsFromValue(value: unknown): string[];
13
+ export { looksLikeUrl };
14
+ //# sourceMappingURL=images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/lib/images.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,iBAAS,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,MAAM,CAAC,GAC1C,OAAO,CAIT;AAqBD,mFAAmF;AACnF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC1C,WAAW,EAAE,CAmBf;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAYtD;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}