@momentumcms/core 0.1.10 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.2.0 (2026-02-17)
2
+
3
+ ### 🚀 Features
4
+
5
+ - add named tabs support with nested data grouping and tab UI improvements ([63ab63e](https://github.com/DonaldMurillo/momentum-cms/commit/63ab63e))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - Claude Opus 4.6
10
+ - Donald Murillo @DonaldMurillo
11
+
1
12
  ## 0.1.10 (2026-02-17)
2
13
 
3
14
  ### 🩹 Fixes
@@ -36,12 +36,26 @@ var import_node_path = require("node:path");
36
36
  var import_node_url = require("node:url");
37
37
 
38
38
  // libs/core/src/lib/fields/field.types.ts
39
+ function isNamedTab(tab) {
40
+ return typeof tab.name === "string" && tab.name.length > 0;
41
+ }
39
42
  function flattenDataFields(fields) {
40
43
  const result = [];
41
44
  for (const field of fields) {
42
45
  if (field.type === "tabs") {
43
46
  for (const tab of field.tabs) {
44
- result.push(...flattenDataFields(tab.fields));
47
+ if (isNamedTab(tab)) {
48
+ const syntheticGroup = {
49
+ name: tab.name,
50
+ type: "group",
51
+ label: tab.label,
52
+ description: tab.description,
53
+ fields: tab.fields
54
+ };
55
+ result.push(syntheticGroup);
56
+ } else {
57
+ result.push(...flattenDataFields(tab.fields));
58
+ }
45
59
  }
46
60
  } else if (field.type === "collapsible" || field.type === "row") {
47
61
  result.push(...flattenDataFields(field.fields));
@@ -262,11 +276,7 @@ function generateTypes(config) {
262
276
  const hasTimestamps = collection.timestamps !== false;
263
277
  for (const field of dataFields) {
264
278
  if (field.type === "blocks") {
265
- const blockResult = generateBlockTypes(
266
- collection.slug,
267
- field.name,
268
- field
269
- );
279
+ const blockResult = generateBlockTypes(collection.slug, field.name, field);
270
280
  blockDeclarations.push(blockResult.declarations);
271
281
  }
272
282
  }
@@ -280,11 +290,7 @@ function generateTypes(config) {
280
290
  const optional = field.required ? "" : "?";
281
291
  const propName = needsQuoting2(field.name) ? safeQuote(field.name) : field.name;
282
292
  if (field.type === "blocks") {
283
- const blockResult = generateBlockTypes(
284
- collection.slug,
285
- field.name,
286
- field
287
- );
293
+ const blockResult = generateBlockTypes(collection.slug, field.name, field);
288
294
  lines.push(` ${propName}${optional}: ${blockResult.unionTypeName}[];`);
289
295
  } else {
290
296
  const tsType = fieldTypeToTS(field);
@@ -554,6 +560,9 @@ function serializeTabsArray(tabs, indent) {
554
560
  return "[]";
555
561
  const items = tabs.map((tab) => {
556
562
  const parts = [];
563
+ if (tab.name) {
564
+ parts.push(`${indent} name: ${JSON.stringify(tab.name)}`);
565
+ }
557
566
  parts.push(`${indent} label: ${JSON.stringify(tab.label)}`);
558
567
  if (tab.description) {
559
568
  parts.push(`${indent} description: ${JSON.stringify(tab.description)}`);
@@ -5,12 +5,26 @@ import { dirname, resolve, relative } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
 
7
7
  // libs/core/src/lib/fields/field.types.ts
8
+ function isNamedTab(tab) {
9
+ return typeof tab.name === "string" && tab.name.length > 0;
10
+ }
8
11
  function flattenDataFields(fields) {
9
12
  const result = [];
10
13
  for (const field of fields) {
11
14
  if (field.type === "tabs") {
12
15
  for (const tab of field.tabs) {
13
- result.push(...flattenDataFields(tab.fields));
16
+ if (isNamedTab(tab)) {
17
+ const syntheticGroup = {
18
+ name: tab.name,
19
+ type: "group",
20
+ label: tab.label,
21
+ description: tab.description,
22
+ fields: tab.fields
23
+ };
24
+ result.push(syntheticGroup);
25
+ } else {
26
+ result.push(...flattenDataFields(tab.fields));
27
+ }
14
28
  }
15
29
  } else if (field.type === "collapsible" || field.type === "row") {
16
30
  result.push(...flattenDataFields(field.fields));
@@ -231,11 +245,7 @@ function generateTypes(config) {
231
245
  const hasTimestamps = collection.timestamps !== false;
232
246
  for (const field of dataFields) {
233
247
  if (field.type === "blocks") {
234
- const blockResult = generateBlockTypes(
235
- collection.slug,
236
- field.name,
237
- field
238
- );
248
+ const blockResult = generateBlockTypes(collection.slug, field.name, field);
239
249
  blockDeclarations.push(blockResult.declarations);
240
250
  }
241
251
  }
@@ -249,11 +259,7 @@ function generateTypes(config) {
249
259
  const optional = field.required ? "" : "?";
250
260
  const propName = needsQuoting2(field.name) ? safeQuote(field.name) : field.name;
251
261
  if (field.type === "blocks") {
252
- const blockResult = generateBlockTypes(
253
- collection.slug,
254
- field.name,
255
- field
256
- );
262
+ const blockResult = generateBlockTypes(collection.slug, field.name, field);
257
263
  lines.push(` ${propName}${optional}: ${blockResult.unionTypeName}[];`);
258
264
  } else {
259
265
  const tsType = fieldTypeToTS(field);
@@ -523,6 +529,9 @@ function serializeTabsArray(tabs, indent) {
523
529
  return "[]";
524
530
  const items = tabs.map((tab) => {
525
531
  const parts = [];
532
+ if (tab.name) {
533
+ parts.push(`${indent} name: ${JSON.stringify(tab.name)}`);
534
+ }
526
535
  parts.push(`${indent} label: ${JSON.stringify(tab.label)}`);
527
536
  if (tab.description) {
528
537
  parts.push(`${indent} description: ${JSON.stringify(tab.description)}`);
package/index.cjs CHANGED
@@ -53,6 +53,7 @@ __export(src_exports, {
53
53
  humanizeFieldName: () => humanizeFieldName,
54
54
  isAuthenticated: () => isAuthenticated,
55
55
  isLayoutField: () => isLayoutField,
56
+ isNamedTab: () => isNamedTab,
56
57
  isOwner: () => isOwner,
57
58
  json: () => json,
58
59
  not: () => not,
@@ -127,6 +128,9 @@ var ReferentialIntegrityError = class extends Error {
127
128
  this.constraint = constraint;
128
129
  }
129
130
  };
131
+ function isNamedTab(tab) {
132
+ return typeof tab.name === "string" && tab.name.length > 0;
133
+ }
130
134
  function isLayoutField(field) {
131
135
  return LAYOUT_FIELD_TYPES.has(field.type);
132
136
  }
@@ -135,7 +139,18 @@ function flattenDataFields(fields) {
135
139
  for (const field of fields) {
136
140
  if (field.type === "tabs") {
137
141
  for (const tab of field.tabs) {
138
- result.push(...flattenDataFields(tab.fields));
142
+ if (isNamedTab(tab)) {
143
+ const syntheticGroup = {
144
+ name: tab.name,
145
+ type: "group",
146
+ label: tab.label,
147
+ description: tab.description,
148
+ fields: tab.fields
149
+ };
150
+ result.push(syntheticGroup);
151
+ } else {
152
+ result.push(...flattenDataFields(tab.fields));
153
+ }
139
154
  }
140
155
  } else if (field.type === "collapsible" || field.type === "row") {
141
156
  result.push(...flattenDataFields(field.fields));
@@ -709,6 +724,7 @@ function createSeedHelpers() {
709
724
  humanizeFieldName,
710
725
  isAuthenticated,
711
726
  isLayoutField,
727
+ isNamedTab,
712
728
  isOwner,
713
729
  json,
714
730
  not,
package/index.js CHANGED
@@ -51,6 +51,9 @@ var ReferentialIntegrityError = class extends Error {
51
51
  this.constraint = constraint;
52
52
  }
53
53
  };
54
+ function isNamedTab(tab) {
55
+ return typeof tab.name === "string" && tab.name.length > 0;
56
+ }
54
57
  function isLayoutField(field) {
55
58
  return LAYOUT_FIELD_TYPES.has(field.type);
56
59
  }
@@ -59,7 +62,18 @@ function flattenDataFields(fields) {
59
62
  for (const field of fields) {
60
63
  if (field.type === "tabs") {
61
64
  for (const tab of field.tabs) {
62
- result.push(...flattenDataFields(tab.fields));
65
+ if (isNamedTab(tab)) {
66
+ const syntheticGroup = {
67
+ name: tab.name,
68
+ type: "group",
69
+ label: tab.label,
70
+ description: tab.description,
71
+ fields: tab.fields
72
+ };
73
+ result.push(syntheticGroup);
74
+ } else {
75
+ result.push(...flattenDataFields(tab.fields));
76
+ }
63
77
  }
64
78
  } else if (field.type === "collapsible" || field.type === "row") {
65
79
  result.push(...flattenDataFields(field.fields));
@@ -632,6 +646,7 @@ export {
632
646
  humanizeFieldName,
633
647
  isAuthenticated,
634
648
  isLayoutField,
649
+ isNamedTab,
635
650
  isOwner,
636
651
  json,
637
652
  not,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momentumcms/core",
3
- "version": "0.1.10",
3
+ "version": "0.2.0",
4
4
  "description": "Core collection config, fields, hooks, and access control for Momentum CMS",
5
5
  "license": "MIT",
6
6
  "author": "Momentum CMS Contributors",
@@ -31,6 +31,7 @@ interface FieldDefinition {
31
31
  editor?: Record<string, unknown>;
32
32
  }>;
33
33
  tabs?: Array<{
34
+ name?: string;
34
35
  label: string;
35
36
  description?: string;
36
37
  fields: FieldDefinition[];
@@ -239,10 +239,16 @@ export interface SlugField extends BaseField {
239
239
  }
240
240
  /** Tab definition within a tabs layout field */
241
241
  export interface TabConfig {
242
+ /** When present, creates a nested data structure (like a group). Omit for layout-only tabs. */
243
+ name?: string;
242
244
  label: string;
243
245
  description?: string;
244
246
  fields: Field[];
245
247
  }
248
+ /** Type guard: returns true if the tab has a non-empty name (stores nested data). */
249
+ export declare function isNamedTab(tab: TabConfig): tab is TabConfig & {
250
+ name: string;
251
+ };
246
252
  /** Tabs layout field - organizes fields into tabbed sections */
247
253
  export interface TabsField extends BaseField {
248
254
  type: 'tabs';