nn-widgets 0.1.1 → 0.1.2

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.
@@ -35,6 +35,10 @@ export type WidgetItemIcon = string | {
35
35
  radius?: number;
36
36
  /** Background color behind the icon (hex) */
37
37
  backgroundColor?: string;
38
+ /** Border width in points @default 0 */
39
+ borderWidth?: number;
40
+ /** Border color (hex) @default transparent */
41
+ borderColor?: string;
38
42
  };
39
43
  /**
40
44
  * Text configuration for a widget item.
@@ -66,6 +70,56 @@ export interface WidgetListItem {
66
70
  /** Per-item deep link URL */
67
71
  deepLink?: string;
68
72
  }
73
+ /**
74
+ * Justify content options for flex rows (similar to CSS flexbox)
75
+ */
76
+ export type FlexJustifyContent = "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
77
+ /**
78
+ * Align items options for flex rows (similar to CSS flexbox)
79
+ */
80
+ export type FlexAlignItems = "flex-start" | "flex-end" | "center" | "stretch";
81
+ /**
82
+ * Row configuration for flex layout.
83
+ * Can be a simple array of ratios or an object with alignment options.
84
+ */
85
+ export type FlexRowConfig = number[] | {
86
+ /** Relative widths of items in this row (should sum to 1) */
87
+ ratios: number[];
88
+ /** How to distribute space horizontally @default "flex-start" */
89
+ justifyContent?: FlexJustifyContent;
90
+ /** How to align items vertically @default "center" */
91
+ alignItems?: FlexAlignItems;
92
+ };
93
+ /**
94
+ * Flex layout configuration per widget family.
95
+ *
96
+ * Each row can be:
97
+ * - Simple: array of numbers representing relative widths
98
+ * - Detailed: object with ratios and optional justifyContent/alignItems
99
+ *
100
+ * @example Simple format (backward compatible):
101
+ * ```ts
102
+ * layout: {
103
+ * systemMedium: [[1], [0.5, 0.5]]
104
+ * }
105
+ * ```
106
+ *
107
+ * @example Detailed format with per-row alignment:
108
+ * ```ts
109
+ * layout: {
110
+ * systemMedium: [
111
+ * { ratios: [1], justifyContent: "flex-start" },
112
+ * { ratios: [0.25, 0.25, 0.25, 0.25], justifyContent: "space-evenly" }
113
+ * ]
114
+ * }
115
+ * ```
116
+ */
117
+ export interface FlexLayoutConfig {
118
+ systemSmall?: FlexRowConfig[];
119
+ systemMedium?: FlexRowConfig[];
120
+ systemLarge?: FlexRowConfig[];
121
+ systemExtraLarge?: FlexRowConfig[];
122
+ }
69
123
  /**
70
124
  * Structured data payload for `updateWidget()`.
71
125
  */
@@ -76,8 +130,10 @@ export interface WidgetDataPayload {
76
130
  subtitle?: string;
77
131
  /** Numeric value (for single-type widgets) */
78
132
  value?: number;
79
- /** List items (for list-type widgets) */
133
+ /** List items (for list/grid/flex-grid widgets) */
80
134
  items?: WidgetListItem[];
135
+ /** Flex grid layout configuration (for flex-grid widgets only) */
136
+ layout?: FlexLayoutConfig;
81
137
  }
82
138
  /**
83
139
  * Widget module interface
@@ -1 +1 @@
1
- {"version":3,"file":"NNWidgets.types.d.ts","sourceRoot":"","sources":["../src/NNWidgets.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,UAAU,GACV,SAAS,GACT,UAAU,GACV,aAAa,GACb,MAAM,GACN,UAAU,GACV,QAAQ,GACR,QAAQ,GACR,OAAO,CAAC;AAEZ;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1E;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IACE,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IACE,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,kBAAkB;IAClB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B,CAAC;AAEN;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,wEAAwE;IACxE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,2CAA2C;IAC3C,KAAK,EAAE,cAAc,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElD;;OAEG;IACH,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAErC;;;;;;;OAOG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE5E;;OAEG;IACH,YAAY,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE5D;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC;CACxB"}
1
+ {"version":3,"file":"NNWidgets.types.d.ts","sourceRoot":"","sources":["../src/NNWidgets.types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"NNWidgets.types.js","sourceRoot":"","sources":["../src/NNWidgets.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Widget data that can be shared between app and widget (legacy flat format)\n */\nexport interface WidgetData {\n [key: string]: string | number | boolean | null;\n}\n\n/**\n * Configuration for updating widget\n */\nexport interface WidgetUpdateConfig {\n /**\n * Widget kind identifier (iOS) or widget class name (Android)\n */\n widgetName?: string;\n}\n\n// ──────────────────────────────────────────────\n// Rich widget data types (for list/single widgets)\n// ──────────────────────────────────────────────\n\n/**\n * System font size keys (maps to SwiftUI Font / Android sp).\n */\nexport type WidgetFontSize =\n | \"caption2\"\n | \"caption\"\n | \"footnote\"\n | \"subheadline\"\n | \"body\"\n | \"headline\"\n | \"title3\"\n | \"title2\"\n | \"title\";\n\n/**\n * Font weight keys.\n */\nexport type WidgetFontWeight = \"regular\" | \"medium\" | \"semibold\" | \"bold\";\n\n/**\n * Icon configuration for a widget item.\n * - string: SF Symbol name (iOS) or drawable resource name (Android)\n * - object: icon with extra styling\n */\nexport type WidgetItemIcon =\n | string\n | {\n /** Icon name: SF Symbol (iOS), drawable name (Android), or bundled icon name */\n url: string;\n /** Icon size in points/dp @default 32 */\n size?: number;\n /** Border radius (0 = square, size/2 = circle) @default 0 */\n radius?: number;\n /** Background color behind the icon (hex) */\n backgroundColor?: string;\n };\n\n/**\n * Text configuration for a widget item.\n * - string: plain text\n * - object: text with custom styling\n */\nexport type WidgetItemText =\n | string\n | {\n /** The text content */\n text: string;\n /** Text color (hex). Falls back to widget-level style. */\n color?: string;\n /** Font size key */\n fontSize?: WidgetFontSize;\n /** Font weight */\n fontWeight?: WidgetFontWeight;\n };\n\n/**\n * A single item in a list-type widget.\n */\nexport interface WidgetListItem {\n /** Left icon (SF Symbol name, bundled icon, or object with styling) */\n icon?: WidgetItemIcon;\n /** Right icon (SF Symbol name, bundled icon, or object with styling) */\n rightIcon?: WidgetItemIcon;\n /** Title text (string or styled object) */\n title: WidgetItemText;\n /** Description text (string or styled object) */\n description?: WidgetItemText;\n /** Per-item deep link URL */\n deepLink?: string;\n}\n\n/**\n * Structured data payload for `updateWidget()`.\n */\nexport interface WidgetDataPayload {\n /** Title text (for single/list fallback) */\n title?: string;\n /** Subtitle text (for single/list fallback) */\n subtitle?: string;\n /** Numeric value (for single-type widgets) */\n value?: number;\n /** List items (for list-type widgets) */\n items?: WidgetListItem[];\n}\n\n/**\n * Widget module interface\n */\nexport interface NNWidgetsModuleType {\n /**\n * Set shared data that widgets can read (legacy flat key-value format).\n * Keys are stored with `widget_` prefix in UserDefaults/SharedPreferences.\n */\n setWidgetData(data: WidgetData): Promise<boolean>;\n\n /**\n * Get current shared widget data (legacy format)\n */\n getWidgetData(): Promise<WidgetData>;\n\n /**\n * Update a specific widget with structured data.\n * Automatically serializes items to JSON and reloads the widget timeline.\n *\n * @param widgetName - The widget kind identifier (matches `name` in app.json config)\n * @param data - Structured widget data payload\n * @returns Promise<boolean> - Success status\n */\n updateWidget(widgetName: string, data: WidgetDataPayload): Promise<boolean>;\n\n /**\n * Request widget to reload/update its timeline (iOS) or update (Android)\n */\n reloadWidget(config?: WidgetUpdateConfig): Promise<boolean>;\n\n /**\n * Check if widgets are supported on this device\n */\n isSupported(): boolean;\n}\n"]}
1
+ {"version":3,"file":"NNWidgets.types.js","sourceRoot":"","sources":["../src/NNWidgets.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Widget data that can be shared between app and widget (legacy flat format)\n */\nexport interface WidgetData {\n [key: string]: string | number | boolean | null;\n}\n\n/**\n * Configuration for updating widget\n */\nexport interface WidgetUpdateConfig {\n /**\n * Widget kind identifier (iOS) or widget class name (Android)\n */\n widgetName?: string;\n}\n\n// ──────────────────────────────────────────────\n// Rich widget data types (for list/single widgets)\n// ──────────────────────────────────────────────\n\n/**\n * System font size keys (maps to SwiftUI Font / Android sp).\n */\nexport type WidgetFontSize =\n | \"caption2\"\n | \"caption\"\n | \"footnote\"\n | \"subheadline\"\n | \"body\"\n | \"headline\"\n | \"title3\"\n | \"title2\"\n | \"title\";\n\n/**\n * Font weight keys.\n */\nexport type WidgetFontWeight = \"regular\" | \"medium\" | \"semibold\" | \"bold\";\n\n/**\n * Icon configuration for a widget item.\n * - string: SF Symbol name (iOS) or drawable resource name (Android)\n * - object: icon with extra styling\n */\nexport type WidgetItemIcon =\n | string\n | {\n /** Icon name: SF Symbol (iOS), drawable name (Android), or bundled icon name */\n url: string;\n /** Icon size in points/dp @default 32 */\n size?: number;\n /** Border radius (0 = square, size/2 = circle) @default 0 */\n radius?: number;\n /** Background color behind the icon (hex) */\n backgroundColor?: string;\n /** Border width in points @default 0 */\n borderWidth?: number;\n /** Border color (hex) @default transparent */\n borderColor?: string;\n };\n\n/**\n * Text configuration for a widget item.\n * - string: plain text\n * - object: text with custom styling\n */\nexport type WidgetItemText =\n | string\n | {\n /** The text content */\n text: string;\n /** Text color (hex). Falls back to widget-level style. */\n color?: string;\n /** Font size key */\n fontSize?: WidgetFontSize;\n /** Font weight */\n fontWeight?: WidgetFontWeight;\n };\n\n/**\n * A single item in a list-type widget.\n */\nexport interface WidgetListItem {\n /** Left icon (SF Symbol name, bundled icon, or object with styling) */\n icon?: WidgetItemIcon;\n /** Right icon (SF Symbol name, bundled icon, or object with styling) */\n rightIcon?: WidgetItemIcon;\n /** Title text (string or styled object) */\n title: WidgetItemText;\n /** Description text (string or styled object) */\n description?: WidgetItemText;\n /** Per-item deep link URL */\n deepLink?: string;\n}\n\n/**\n * Justify content options for flex rows (similar to CSS flexbox)\n */\nexport type FlexJustifyContent = \n | \"flex-start\" \n | \"flex-end\" \n | \"center\" \n | \"space-between\" \n | \"space-around\" \n | \"space-evenly\";\n\n/**\n * Align items options for flex rows (similar to CSS flexbox)\n */\nexport type FlexAlignItems = \n | \"flex-start\" \n | \"flex-end\" \n | \"center\" \n | \"stretch\";\n\n/**\n * Row configuration for flex layout.\n * Can be a simple array of ratios or an object with alignment options.\n */\nexport type FlexRowConfig = \n | number[] \n | {\n /** Relative widths of items in this row (should sum to 1) */\n ratios: number[];\n /** How to distribute space horizontally @default \"flex-start\" */\n justifyContent?: FlexJustifyContent;\n /** How to align items vertically @default \"center\" */\n alignItems?: FlexAlignItems;\n };\n\n/**\n * Flex layout configuration per widget family.\n * \n * Each row can be:\n * - Simple: array of numbers representing relative widths\n * - Detailed: object with ratios and optional justifyContent/alignItems\n *\n * @example Simple format (backward compatible):\n * ```ts\n * layout: {\n * systemMedium: [[1], [0.5, 0.5]]\n * }\n * ```\n *\n * @example Detailed format with per-row alignment:\n * ```ts\n * layout: {\n * systemMedium: [\n * { ratios: [1], justifyContent: \"flex-start\" },\n * { ratios: [0.25, 0.25, 0.25, 0.25], justifyContent: \"space-evenly\" }\n * ]\n * }\n * ```\n */\nexport interface FlexLayoutConfig {\n systemSmall?: FlexRowConfig[];\n systemMedium?: FlexRowConfig[];\n systemLarge?: FlexRowConfig[];\n systemExtraLarge?: FlexRowConfig[];\n}\n\n/**\n * Structured data payload for `updateWidget()`.\n */\nexport interface WidgetDataPayload {\n /** Title text (for single/list fallback) */\n title?: string;\n /** Subtitle text (for single/list fallback) */\n subtitle?: string;\n /** Numeric value (for single-type widgets) */\n value?: number;\n /** List items (for list/grid/flex-grid widgets) */\n items?: WidgetListItem[];\n /** Flex grid layout configuration (for flex-grid widgets only) */\n layout?: FlexLayoutConfig;\n}\n\n/**\n * Widget module interface\n */\nexport interface NNWidgetsModuleType {\n /**\n * Set shared data that widgets can read (legacy flat key-value format).\n * Keys are stored with `widget_` prefix in UserDefaults/SharedPreferences.\n */\n setWidgetData(data: WidgetData): Promise<boolean>;\n\n /**\n * Get current shared widget data (legacy format)\n */\n getWidgetData(): Promise<WidgetData>;\n\n /**\n * Update a specific widget with structured data.\n * Automatically serializes items to JSON and reloads the widget timeline.\n *\n * @param widgetName - The widget kind identifier (matches `name` in app.json config)\n * @param data - Structured widget data payload\n * @returns Promise<boolean> - Success status\n */\n updateWidget(widgetName: string, data: WidgetDataPayload): Promise<boolean>;\n\n /**\n * Request widget to reload/update its timeline (iOS) or update (Android)\n */\n reloadWidget(config?: WidgetUpdateConfig): Promise<boolean>;\n\n /**\n * Check if widgets are supported on this device\n */\n isSupported(): boolean;\n}\n"]}
package/build/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { WidgetData, WidgetDataPayload, WidgetListItem, WidgetItemIcon, WidgetItemText, WidgetUpdateConfig, NNWidgetsModuleType } from "./NNWidgets.types";
2
- export type { WidgetData, WidgetDataPayload, WidgetListItem, WidgetItemIcon, WidgetItemText, WidgetUpdateConfig, NNWidgetsModuleType, };
1
+ import type { WidgetData, WidgetDataPayload, WidgetListItem, WidgetItemIcon, WidgetItemText, WidgetUpdateConfig, NNWidgetsModuleType, FlexLayoutConfig } from "./NNWidgets.types";
2
+ export type { WidgetData, WidgetDataPayload, WidgetListItem, WidgetItemIcon, WidgetItemText, WidgetUpdateConfig, NNWidgetsModuleType, FlexLayoutConfig, };
3
3
  /**
4
4
  * NNWidgets - Native widget integration for React Native / Expo
5
5
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,mBAAmB,GACpB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,mBAqJvB,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,GACjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,mBAyJvB,CAAC;AAEF,eAAe,SAAS,CAAC"}
package/build/index.js CHANGED
@@ -97,6 +97,10 @@ export const NNWidgets = {
97
97
  // Serialize items array to JSON string for native storage
98
98
  flatData[`${widgetName}_items`] = JSON.stringify(data.items);
99
99
  }
100
+ if (data.layout !== undefined) {
101
+ // Serialize layout config to JSON for flex-grid widgets
102
+ flatData[`${widgetName}_layout`] = JSON.stringify(data.layout);
103
+ }
100
104
  const success = await NNWidgetsModule.setWidgetData(flatData);
101
105
  if (success) {
102
106
  await NNWidgetsModule.reloadWidget(widgetName);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAqBhD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAwB;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,aAAa,CAAC,IAAgB;QAClC,IAAI,CAAC;YACH,OAAO,MAAM,eAAe,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YAC/D,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC;YACH,OAAO,MAAM,eAAe,CAAC,aAAa,EAAE,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YAC/D,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,KAAK,CAAC,YAAY,CAChB,UAAkB,EAClB,IAAuB;QAEvB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAe,EAAE,CAAC;YAEhC,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC7B,QAAQ,CAAC,GAAG,UAAU,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YAC/C,CAAC;YACD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAChC,QAAQ,CAAC,GAAG,UAAU,WAAW,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;YACrD,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC7B,QAAQ,CAAC,GAAG,UAAU,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YAC/C,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC7B,0DAA0D;gBAC1D,QAAQ,CAAC,GAAG,UAAU,QAAQ,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC/D,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC9D,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,eAAe,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;YACjD,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CACX,wCAAwC,UAAU,IAAI,EACtD,KAAK,CACN,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,YAAY,CAAC,MAA2B;QAC5C,IAAI,CAAC;YACH,OAAO,MAAM,eAAe,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,IAAI,IAAI,CAAC,CAAC;QACxE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,KAAK,CAAC,CAAC;YAC7D,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,IAAI,CAAC;YACH,OAAO,eAAe,CAAC,WAAW,EAAE,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF,CAAC;AAEF,eAAe,SAAS,CAAC","sourcesContent":["import NNWidgetsModule from \"./NNWidgetsModule\";\nimport type {\n WidgetData,\n WidgetDataPayload,\n WidgetListItem,\n WidgetItemIcon,\n WidgetItemText,\n WidgetUpdateConfig,\n NNWidgetsModuleType,\n} from \"./NNWidgets.types\";\n\nexport type {\n WidgetData,\n WidgetDataPayload,\n WidgetListItem,\n WidgetItemIcon,\n WidgetItemText,\n WidgetUpdateConfig,\n NNWidgetsModuleType,\n};\n\n/**\n * NNWidgets - Native widget integration for React Native / Expo\n *\n * This module provides APIs to communicate between your React Native app\n * and native widgets (iOS WidgetKit & Android App Widgets).\n */\nexport const NNWidgets: NNWidgetsModuleType = {\n /**\n * Set shared data that widgets can read (legacy flat key-value format).\n * Data is stored in App Groups (iOS) or SharedPreferences (Android).\n *\n * @param data - Key-value pairs to share with widget\n * @returns Promise<boolean> - Success status\n *\n * @example\n * ```ts\n * await NNWidgets.setWidgetData({\n * userName: 'John',\n * points: 100,\n * isLoggedIn: true\n * });\n * ```\n */\n async setWidgetData(data: WidgetData): Promise<boolean> {\n try {\n return await NNWidgetsModule.setWidgetData(data);\n } catch (error) {\n console.error(\"[NNWidgets] Failed to set widget data:\", error);\n return false;\n }\n },\n\n /**\n * Get current shared widget data.\n *\n * @returns Promise<WidgetData> - Current shared data\n */\n async getWidgetData(): Promise<WidgetData> {\n try {\n return await NNWidgetsModule.getWidgetData();\n } catch (error) {\n console.error(\"[NNWidgets] Failed to get widget data:\", error);\n return {};\n }\n },\n\n /**\n * Update a specific widget with structured data.\n * Automatically serializes items to JSON, stores data with proper key prefixes,\n * and reloads the widget timeline.\n *\n * @param widgetName - The widget kind identifier (matches `name` in app.json widgets config)\n * @param data - Structured widget data payload\n * @returns Promise<boolean> - Success status\n *\n * @example\n * ```ts\n * // Update a list-type widget\n * await NNWidgets.updateWidget('SessionsWidget', {\n * items: [\n * {\n * icon: 'brainstorm',\n * title: { text: 'Morning Focus', color: '#1E1E2E', fontWeight: 'bold' },\n * description: '25 min session',\n * deepLink: 'myapp://session/123',\n * },\n * {\n * icon: { url: 'meditation', size: 28, radius: 14 },\n * title: 'Evening Wind Down',\n * description: { text: '15 min', color: '#888888' },\n * },\n * ],\n * });\n *\n * // Update a single-type widget\n * await NNWidgets.updateWidget('StatsWidget', {\n * title: 'Focus Time',\n * subtitle: 'This week',\n * value: 142,\n * });\n * ```\n */\n async updateWidget(\n widgetName: string,\n data: WidgetDataPayload,\n ): Promise<boolean> {\n try {\n const flatData: WidgetData = {};\n\n if (data.title !== undefined) {\n flatData[`${widgetName}_title`] = data.title;\n }\n if (data.subtitle !== undefined) {\n flatData[`${widgetName}_subtitle`] = data.subtitle;\n }\n if (data.value !== undefined) {\n flatData[`${widgetName}_value`] = data.value;\n }\n if (data.items !== undefined) {\n // Serialize items array to JSON string for native storage\n flatData[`${widgetName}_items`] = JSON.stringify(data.items);\n }\n\n const success = await NNWidgetsModule.setWidgetData(flatData);\n if (success) {\n await NNWidgetsModule.reloadWidget(widgetName);\n }\n return success;\n } catch (error) {\n console.error(\n `[NNWidgets] Failed to update widget \"${widgetName}\":`,\n error,\n );\n return false;\n }\n },\n\n /**\n * Request widget to reload/update.\n * - iOS: Reloads widget timeline\n * - Android: Triggers widget update broadcast\n *\n * @param config - Optional configuration\n * @returns Promise<boolean> - Success status\n *\n * @example\n * ```ts\n * // Update all widgets\n * await NNWidgets.reloadWidget();\n *\n * // Update specific widget\n * await NNWidgets.reloadWidget({ widgetName: 'MyWidget' });\n * ```\n */\n async reloadWidget(config?: WidgetUpdateConfig): Promise<boolean> {\n try {\n return await NNWidgetsModule.reloadWidget(config?.widgetName || null);\n } catch (error) {\n console.error(\"[NNWidgets] Failed to reload widget:\", error);\n return false;\n }\n },\n\n /**\n * Check if widgets are supported on this device.\n *\n * @returns boolean - True if widgets are supported\n */\n isSupported(): boolean {\n try {\n return NNWidgetsModule.isSupported();\n } catch (error) {\n return false;\n }\n },\n};\n\nexport default NNWidgets;\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import NNWidgetsModule from \"./NNWidgetsModule\";\nimport type {\n WidgetData,\n WidgetDataPayload,\n WidgetListItem,\n WidgetItemIcon,\n WidgetItemText,\n WidgetUpdateConfig,\n NNWidgetsModuleType,\n FlexLayoutConfig,\n} from \"./NNWidgets.types\";\n\nexport type {\n WidgetData,\n WidgetDataPayload,\n WidgetListItem,\n WidgetItemIcon,\n WidgetItemText,\n WidgetUpdateConfig,\n NNWidgetsModuleType,\n FlexLayoutConfig,\n};\n\n/**\n * NNWidgets - Native widget integration for React Native / Expo\n *\n * This module provides APIs to communicate between your React Native app\n * and native widgets (iOS WidgetKit & Android App Widgets).\n */\nexport const NNWidgets: NNWidgetsModuleType = {\n /**\n * Set shared data that widgets can read (legacy flat key-value format).\n * Data is stored in App Groups (iOS) or SharedPreferences (Android).\n *\n * @param data - Key-value pairs to share with widget\n * @returns Promise<boolean> - Success status\n *\n * @example\n * ```ts\n * await NNWidgets.setWidgetData({\n * userName: 'John',\n * points: 100,\n * isLoggedIn: true\n * });\n * ```\n */\n async setWidgetData(data: WidgetData): Promise<boolean> {\n try {\n return await NNWidgetsModule.setWidgetData(data);\n } catch (error) {\n console.error(\"[NNWidgets] Failed to set widget data:\", error);\n return false;\n }\n },\n\n /**\n * Get current shared widget data.\n *\n * @returns Promise<WidgetData> - Current shared data\n */\n async getWidgetData(): Promise<WidgetData> {\n try {\n return await NNWidgetsModule.getWidgetData();\n } catch (error) {\n console.error(\"[NNWidgets] Failed to get widget data:\", error);\n return {};\n }\n },\n\n /**\n * Update a specific widget with structured data.\n * Automatically serializes items to JSON, stores data with proper key prefixes,\n * and reloads the widget timeline.\n *\n * @param widgetName - The widget kind identifier (matches `name` in app.json widgets config)\n * @param data - Structured widget data payload\n * @returns Promise<boolean> - Success status\n *\n * @example\n * ```ts\n * // Update a list-type widget\n * await NNWidgets.updateWidget('SessionsWidget', {\n * items: [\n * {\n * icon: 'brainstorm',\n * title: { text: 'Morning Focus', color: '#1E1E2E', fontWeight: 'bold' },\n * description: '25 min session',\n * deepLink: 'myapp://session/123',\n * },\n * {\n * icon: { url: 'meditation', size: 28, radius: 14 },\n * title: 'Evening Wind Down',\n * description: { text: '15 min', color: '#888888' },\n * },\n * ],\n * });\n *\n * // Update a single-type widget\n * await NNWidgets.updateWidget('StatsWidget', {\n * title: 'Focus Time',\n * subtitle: 'This week',\n * value: 142,\n * });\n * ```\n */\n async updateWidget(\n widgetName: string,\n data: WidgetDataPayload,\n ): Promise<boolean> {\n try {\n const flatData: WidgetData = {};\n\n if (data.title !== undefined) {\n flatData[`${widgetName}_title`] = data.title;\n }\n if (data.subtitle !== undefined) {\n flatData[`${widgetName}_subtitle`] = data.subtitle;\n }\n if (data.value !== undefined) {\n flatData[`${widgetName}_value`] = data.value;\n }\n if (data.items !== undefined) {\n // Serialize items array to JSON string for native storage\n flatData[`${widgetName}_items`] = JSON.stringify(data.items);\n }\n if (data.layout !== undefined) {\n // Serialize layout config to JSON for flex-grid widgets\n flatData[`${widgetName}_layout`] = JSON.stringify(data.layout);\n }\n\n const success = await NNWidgetsModule.setWidgetData(flatData);\n if (success) {\n await NNWidgetsModule.reloadWidget(widgetName);\n }\n return success;\n } catch (error) {\n console.error(\n `[NNWidgets] Failed to update widget \"${widgetName}\":`,\n error,\n );\n return false;\n }\n },\n\n /**\n * Request widget to reload/update.\n * - iOS: Reloads widget timeline\n * - Android: Triggers widget update broadcast\n *\n * @param config - Optional configuration\n * @returns Promise<boolean> - Success status\n *\n * @example\n * ```ts\n * // Update all widgets\n * await NNWidgets.reloadWidget();\n *\n * // Update specific widget\n * await NNWidgets.reloadWidget({ widgetName: 'MyWidget' });\n * ```\n */\n async reloadWidget(config?: WidgetUpdateConfig): Promise<boolean> {\n try {\n return await NNWidgetsModule.reloadWidget(config?.widgetName || null);\n } catch (error) {\n console.error(\"[NNWidgets] Failed to reload widget:\", error);\n return false;\n }\n },\n\n /**\n * Check if widgets are supported on this device.\n *\n * @returns boolean - True if widgets are supported\n */\n isSupported(): boolean {\n try {\n return NNWidgetsModule.isSupported();\n } catch (error) {\n return false;\n }\n },\n};\n\nexport default NNWidgets;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nn-widgets",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Expo config plugin for adding native widgets (iOS WidgetKit & Android App Widgets)",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -8,9 +8,38 @@ export type WidgetFamily = "systemSmall" | "systemMedium" | "systemLarge" | "sys
8
8
  * - `"single"` – Icon + Title + Subtitle centered layout (default for backward compat)
9
9
  * - `"list"` – Vertical list of items, each with icon + title + description
10
10
  * - `"grid"` – Grid of items, each column with icon on top + title at bottom
11
+ * - `"flex-grid"` – Flexible grid with dynamic row layouts (different column ratios per row)
11
12
  * - `"image"` – Full-bleed background image (legacy `image` prop is auto-detected)
12
13
  */
13
- export type WidgetType = "single" | "list" | "grid" | "image";
14
+ export type WidgetType = "single" | "list" | "grid" | "flex-grid" | "image";
15
+ /**
16
+ * Justify content options for flex rows (similar to CSS flexbox)
17
+ */
18
+ export type FlexJustifyContent = "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
19
+ /**
20
+ * Align items options for flex rows (similar to CSS flexbox)
21
+ */
22
+ export type FlexAlignItems = "flex-start" | "flex-end" | "center" | "stretch";
23
+ /**
24
+ * Flex row configuration - can be simple array or detailed object
25
+ */
26
+ export type FlexRowConfig = number[] | {
27
+ /** Relative widths of items in this row (should sum to 1) */
28
+ ratios: number[];
29
+ /** How to distribute space horizontally @default "flex-start" */
30
+ justifyContent?: FlexJustifyContent;
31
+ /** How to align items vertically @default "center" */
32
+ alignItems?: FlexAlignItems;
33
+ };
34
+ /**
35
+ * Flexible grid layout configuration per widget family
36
+ */
37
+ export type FlexLayoutConfig = {
38
+ systemSmall?: FlexRowConfig[];
39
+ systemMedium?: FlexRowConfig[];
40
+ systemLarge?: FlexRowConfig[];
41
+ systemExtraLarge?: FlexRowConfig[];
42
+ };
14
43
  /**
15
44
  * System font size keys (maps to SwiftUI Font / Android sp values).
16
45
  *
@@ -44,6 +73,10 @@ export type WidgetItemIcon = string | {
44
73
  radius?: number;
45
74
  /** Background color behind the icon (hex) */
46
75
  backgroundColor?: string;
76
+ /** Border width in points @default 0 */
77
+ borderWidth?: number;
78
+ /** Border color (hex) @default "#E5E5E5" */
79
+ borderColor?: string;
47
80
  };
48
81
  /**
49
82
  * Text configuration for a list/single item.
@@ -153,6 +186,54 @@ export interface WidgetConfig {
153
186
  * @example "2x2" → 2 columns, 2 rows → max 4 items
154
187
  */
155
188
  gridLayout?: string;
189
+ /**
190
+ * Justify content options for flex rows (similar to CSS flexbox)
191
+ */
192
+ justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
193
+ /**
194
+ * Align items options for flex rows (similar to CSS flexbox)
195
+ */
196
+ alignItems?: "flex-start" | "flex-end" | "center" | "stretch";
197
+ /**
198
+ * Flexible grid layout configuration for type: "flex-grid".
199
+ * Defines row layouts with varying column widths per widget family.
200
+ *
201
+ * Each row can be:
202
+ * - Simple: array of numbers representing relative widths (should sum to 1)
203
+ * - Detailed: object with ratios and optional justifyContent/alignItems
204
+ *
205
+ * @example Simple format (backward compatible):
206
+ * ```json
207
+ * {
208
+ * "systemMedium": [[1], [0.5, 0.5]]
209
+ * }
210
+ * ```
211
+ *
212
+ * @example Detailed format with per-row alignment:
213
+ * ```json
214
+ * {
215
+ * "systemMedium": [
216
+ * { "ratios": [1], "justifyContent": "flex-start" },
217
+ * { "ratios": [0.25, 0.25, 0.25, 0.25], "justifyContent": "space-evenly" }
218
+ * ]
219
+ * }
220
+ * ```
221
+ *
222
+ * justifyContent options:
223
+ * - "flex-start" - items at start (default)
224
+ * - "flex-end" - items at end
225
+ * - "center" - items centered
226
+ * - "space-between" - first/last items at edges, equal space between
227
+ * - "space-around" - equal space around each item
228
+ * - "space-evenly" - equal space everywhere including edges
229
+ *
230
+ * alignItems options:
231
+ * - "flex-start" - align to top
232
+ * - "flex-end" - align to bottom
233
+ * - "center" - align to center (default)
234
+ * - "stretch" - stretch to fill height
235
+ */
236
+ flexLayout?: FlexLayoutConfig;
156
237
  /**
157
238
  * Bundled icon images for use in list/single items at runtime.
158
239
  * Keys are icon names, values are paths to .png files.
@@ -317,6 +398,8 @@ export interface ResolvedWidgetConfig {
317
398
  };
318
399
  /** Grid layout "COLUMNSxROWS" (e.g. "3x2") for grid-type widgets */
319
400
  gridLayout?: string;
401
+ /** Flexible grid layout per widget family for flex-grid type */
402
+ flexLayout?: FlexLayoutConfig;
320
403
  /** Bundled icon images (name → path) */
321
404
  icons?: Record<string, string>;
322
405
  /** Resolved style options */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"withIosWidget.d.ts","sourceRoot":"","sources":["../src/withIosWidget.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIb,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EAEpB,MAAM,SAAS,CAAC;AA+oCjB,eAAO,MAAM,aAAa,EAAE,YAAY,CAAC,oBAAoB,CAugB5D,CAAC;AAMF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GACvB,mBAAmB,CA0GrB;AAED,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"withIosWidget.d.ts","sourceRoot":"","sources":["../src/withIosWidget.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIb,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EAEpB,MAAM,SAAS,CAAC;AA6mDjB,eAAO,MAAM,aAAa,EAAE,YAAY,CAAC,oBAAoB,CAugB5D,CAAC;AAMF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GACvB,mBAAmB,CA2GrB;AAED,eAAe,aAAa,CAAC"}
@@ -46,6 +46,7 @@ function generateDataProviderCode(props, bundleId) {
46
46
  const appGroupId = props.ios.appGroupIdentifier || `group.${bundleId}`;
47
47
  const hasListWidget = props.widgets.some((w) => w.type === "list");
48
48
  const hasSingleWidget = props.widgets.some((w) => w.type === "single");
49
+ const hasFlexGridWidget = props.widgets.some((w) => w.type === "flex-grid");
49
50
  const storageCode = useAppGroups
50
51
  ? `
51
52
  struct WidgetDataProvider {
@@ -108,7 +109,7 @@ struct WidgetDataProvider {
108
109
  }
109
110
  }`;
110
111
  // ── List item model (shared across list-type widgets) ──
111
- const listItemModel = hasListWidget || hasSingleWidget
112
+ const listItemModel = hasListWidget || hasSingleWidget || hasFlexGridWidget
112
113
  ? `
113
114
 
114
115
  // MARK: - Widget Item Models
@@ -118,12 +119,16 @@ struct WidgetIconConfig {
118
119
  let size: CGFloat
119
120
  let radius: CGFloat
120
121
  let backgroundColor: String?
122
+ let borderWidth: CGFloat
123
+ let borderColor: String?
121
124
 
122
125
  init(from dict: [String: Any]) {
123
126
  self.url = dict["url"] as? String ?? ""
124
127
  self.size = CGFloat(dict["size"] as? Double ?? 32)
125
128
  self.radius = CGFloat(dict["radius"] as? Double ?? 0)
126
129
  self.backgroundColor = dict["backgroundColor"] as? String
130
+ self.borderWidth = CGFloat(dict["borderWidth"] as? Double ?? 0)
131
+ self.borderColor = dict["borderColor"] as? String
127
132
  }
128
133
 
129
134
  init(url: String) {
@@ -131,6 +136,8 @@ struct WidgetIconConfig {
131
136
  self.size = 32
132
137
  self.radius = 0
133
138
  self.backgroundColor = nil
139
+ self.borderWidth = 0
140
+ self.borderColor = nil
134
141
  }
135
142
  }
136
143
 
@@ -261,6 +268,13 @@ struct WidgetIconView: View {
261
268
  }
262
269
  .frame(width: config.size, height: config.size)
263
270
  .clipShape(RoundedRectangle(cornerRadius: config.radius))
271
+ .overlay(
272
+ RoundedRectangle(cornerRadius: config.radius)
273
+ .stroke(
274
+ config.borderColor != nil ? Color(hex: config.borderColor!) : Color.clear,
275
+ lineWidth: config.borderWidth
276
+ )
277
+ )
264
278
  }
265
279
  }`
266
280
  : "";
@@ -284,6 +298,10 @@ function generateSingleWidgetCode(w) {
284
298
  if (w.type === "grid") {
285
299
  return generateGridWidgetCode(w, deepLinkLine);
286
300
  }
301
+ // ── Flex-grid mode ──
302
+ if (w.type === "flex-grid") {
303
+ return generateFlexGridWidgetCode(w, deepLinkLine);
304
+ }
287
305
  // ── Single mode (default) ──
288
306
  return generateSingleTypeWidgetCode(w, deepLinkLine);
289
307
  }
@@ -748,6 +766,456 @@ struct ${w.name}EntryView: View {
748
766
  }
749
767
  }
750
768
 
769
+ struct ${w.name}: Widget {
770
+ let kind: String = "${w.name}"
771
+ ${deepLinkLine}
772
+
773
+ var body: some WidgetConfiguration {
774
+ StaticConfiguration(kind: kind, provider: ${w.name}Provider()) { entry in
775
+ if #available(iOS 17.0, *) {
776
+ ${w.name}EntryView(entry: entry)
777
+ .containerBackground(${bgColor}, for: .widget)
778
+ .widgetURL(deepLinkUrl)
779
+ } else {
780
+ ${w.name}EntryView(entry: entry)
781
+ .padding()
782
+ .background(${bgColor})
783
+ .widgetURL(deepLinkUrl)
784
+ }
785
+ }
786
+ .configurationDisplayName("${w.displayName}")
787
+ .description("${w.description}")
788
+ .supportedFamilies([${w.widgetFamilies.map((f) => `.${f}`).join(", ")}])
789
+ }
790
+ }`;
791
+ }
792
+ function generateFlexGridWidgetCode(w, deepLinkLine) {
793
+ const bgColor = w.style?.backgroundColor
794
+ ? `Color(hex: "${w.style.backgroundColor}")`
795
+ : ".clear";
796
+ const titleColor = w.style?.titleColor
797
+ ? `Color(hex: "${w.style.titleColor}")`
798
+ : ".primary";
799
+ const subtitleColor = w.style?.subtitleColor
800
+ ? `Color(hex: "${w.style.subtitleColor}")`
801
+ : ".secondary";
802
+ const accentColor = w.style?.accentColor
803
+ ? `Color(hex: "${w.style.accentColor}")`
804
+ : ".accentColor";
805
+ return `
806
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
807
+ // MARK: - ${w.name} (Flex Grid)
808
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
809
+
810
+ enum FlexJustifyContent: String {
811
+ case flexStart = "flex-start"
812
+ case flexEnd = "flex-end"
813
+ case center = "center"
814
+ case spaceBetween = "space-between"
815
+ case spaceAround = "space-around"
816
+ case spaceEvenly = "space-evenly"
817
+ }
818
+
819
+ enum FlexAlignItems: String {
820
+ case flexStart = "flex-start"
821
+ case flexEnd = "flex-end"
822
+ case center = "center"
823
+ case stretch = "stretch"
824
+ }
825
+
826
+ struct FlexRowLayout {
827
+ let ratios: [Double]
828
+ let justifyContent: FlexJustifyContent
829
+ let alignItems: FlexAlignItems
830
+
831
+ init(ratios: [Double], justifyContent: FlexJustifyContent = .flexStart, alignItems: FlexAlignItems = .center) {
832
+ self.ratios = ratios
833
+ self.justifyContent = justifyContent
834
+ self.alignItems = alignItems
835
+ }
836
+
837
+ static func from(_ value: Any) -> FlexRowLayout? {
838
+ if let ratios = value as? [Double] {
839
+ return FlexRowLayout(ratios: ratios)
840
+ } else if let ratios = value as? [NSNumber] {
841
+ return FlexRowLayout(ratios: ratios.map { $0.doubleValue })
842
+ } else if let dict = value as? [String: Any] {
843
+ var ratios: [Double] = []
844
+ if let r = dict["ratios"] as? [Double] {
845
+ ratios = r
846
+ } else if let r = dict["ratios"] as? [NSNumber] {
847
+ ratios = r.map { $0.doubleValue }
848
+ }
849
+
850
+ let justify = FlexJustifyContent(rawValue: dict["justifyContent"] as? String ?? "flex-start") ?? .flexStart
851
+ let align = FlexAlignItems(rawValue: dict["alignItems"] as? String ?? "center") ?? .center
852
+
853
+ return FlexRowLayout(ratios: ratios, justifyContent: justify, alignItems: align)
854
+ }
855
+ return nil
856
+ }
857
+ }
858
+
859
+ struct ${w.name}Entry: TimelineEntry {
860
+ let date: Date
861
+ let items: [WidgetListItem]
862
+ let layout: [FlexRowLayout]
863
+ let fallbackTitle: String
864
+ let fallbackSubtitle: String
865
+
866
+ static var placeholder: ${w.name}Entry {
867
+ ${w.name}Entry(
868
+ date: Date(),
869
+ items: [],
870
+ layout: [FlexRowLayout(ratios: [1.0])],
871
+ fallbackTitle: "${w.fallbackTitle}",
872
+ fallbackSubtitle: "${w.fallbackSubtitle}"
873
+ )
874
+ }
875
+ }
876
+
877
+ struct ${w.name}Provider: TimelineProvider {
878
+ func placeholder(in context: Context) -> ${w.name}Entry { .placeholder }
879
+
880
+ func getSnapshot(in context: Context, completion: @escaping (${w.name}Entry) -> Void) {
881
+ completion(createEntry(for: context.family))
882
+ }
883
+
884
+ func getTimeline(in context: Context, completion: @escaping (Timeline<${w.name}Entry>) -> Void) {
885
+ let entry = createEntry(for: context.family)
886
+ let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: Date())!
887
+ completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
888
+ }
889
+
890
+ private func createEntry(for family: WidgetFamily) -> ${w.name}Entry {
891
+ var items: [WidgetListItem] = []
892
+ if let rawItems = WidgetDataProvider.getJSON(forKey: "${w.name}_items") as? [[String: Any]] {
893
+ items = rawItems.enumerated().map { WidgetListItem(from: $0.element, index: $0.offset) }
894
+ }
895
+
896
+ // Get layout based on widget family
897
+ var layout: [FlexRowLayout] = [FlexRowLayout(ratios: [1.0])]
898
+ if let layoutDict = WidgetDataProvider.getJSON(forKey: "${w.name}_layout") as? [String: Any] {
899
+ let familyKey: String
900
+ switch family {
901
+ case .systemSmall: familyKey = "systemSmall"
902
+ case .systemMedium: familyKey = "systemMedium"
903
+ case .systemLarge: familyKey = "systemLarge"
904
+ case .systemExtraLarge: familyKey = "systemExtraLarge"
905
+ default: familyKey = "systemMedium"
906
+ }
907
+
908
+ if let familyLayout = layoutDict[familyKey] as? [Any] {
909
+ layout = familyLayout.compactMap { FlexRowLayout.from($0) }
910
+ if layout.isEmpty {
911
+ layout = [FlexRowLayout(ratios: [1.0])]
912
+ }
913
+ }
914
+ }
915
+
916
+ return ${w.name}Entry(
917
+ date: Date(),
918
+ items: items,
919
+ layout: layout,
920
+ fallbackTitle: WidgetDataProvider.getString(forKey: "${w.name}_title", default: "${w.fallbackTitle}"),
921
+ fallbackSubtitle: WidgetDataProvider.getString(forKey: "${w.name}_subtitle", default: "${w.fallbackSubtitle}")
922
+ )
923
+ }
924
+ }
925
+
926
+ // MARK: ${w.name} Flex Grid Cell
927
+
928
+ struct ${w.name}FlexCell: View {
929
+ let item: WidgetListItem
930
+ let defaultTitleColor: Color
931
+ let defaultDescColor: Color
932
+ let defaultAccentColor: Color
933
+
934
+ // Determine display mode based on item data:
935
+ // - hasRightIconOrDesc: show as list row (horizontal)
936
+ // - hasTitle: show as card (icon top, title bottom)
937
+ // - else: icon only
938
+ private var isListMode: Bool {
939
+ item.rightIcon != nil || item.description != nil
940
+ }
941
+
942
+ private var isIconOnly: Bool {
943
+ item.title.text.isEmpty && !isListMode
944
+ }
945
+
946
+ var body: some View {
947
+ if isListMode {
948
+ // List row style: [icon] [title + desc] [rightIcon]
949
+ HStack(spacing: 8) {
950
+ if let icon = item.icon {
951
+ WidgetIconView(config: icon, accentColor: defaultAccentColor)
952
+ }
953
+
954
+ VStack(alignment: .leading, spacing: 2) {
955
+ if !item.title.text.isEmpty {
956
+ Text(item.title.text)
957
+ .font(item.title.resolvedFont(default: .subheadline))
958
+ .fontWeight(item.title.resolvedWeight(default: .semibold))
959
+ .foregroundColor(
960
+ item.title.color != nil
961
+ ? Color(hex: item.title.color!)
962
+ : defaultTitleColor
963
+ )
964
+ .lineLimit(1)
965
+ .truncationMode(.tail)
966
+ }
967
+
968
+ if let desc = item.description, !desc.text.isEmpty {
969
+ Text(desc.text)
970
+ .font(desc.resolvedFont(default: .caption))
971
+ .fontWeight(desc.resolvedWeight(default: .regular))
972
+ .foregroundColor(
973
+ desc.color != nil
974
+ ? Color(hex: desc.color!)
975
+ : defaultDescColor
976
+ )
977
+ .lineLimit(1)
978
+ .truncationMode(.tail)
979
+ }
980
+ }
981
+
982
+ Spacer(minLength: 0)
983
+
984
+ if let rightIcon = item.rightIcon {
985
+ WidgetIconView(config: rightIcon, accentColor: defaultAccentColor)
986
+ }
987
+ }
988
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
989
+ } else if isIconOnly {
990
+ // Icon only mode - centered in cell
991
+ if let icon = item.icon {
992
+ WidgetIconView(config: icon, accentColor: defaultAccentColor)
993
+ }
994
+ } else {
995
+ // Card style: icon on top, title below
996
+ VStack(spacing: 4) {
997
+ if let icon = item.icon {
998
+ WidgetIconView(config: icon, accentColor: defaultAccentColor)
999
+ }
1000
+
1001
+ Text(item.title.text)
1002
+ .font(item.title.resolvedFont(default: .caption))
1003
+ .fontWeight(item.title.resolvedWeight(default: .medium))
1004
+ .foregroundColor(
1005
+ item.title.color != nil
1006
+ ? Color(hex: item.title.color!)
1007
+ : defaultTitleColor
1008
+ )
1009
+ .lineLimit(1)
1010
+ .truncationMode(.tail)
1011
+ }
1012
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
1013
+ }
1014
+ }
1015
+ }
1016
+
1017
+ // MARK: ${w.name} Views
1018
+
1019
+ struct ${w.name}EntryView: View {
1020
+ @Environment(\\.widgetFamily) var family
1021
+ let entry: ${w.name}Entry
1022
+
1023
+ private let titleColor: Color = ${titleColor}
1024
+ private let subtitleColor: Color = ${subtitleColor}
1025
+ private let accentColor: Color = ${accentColor}
1026
+
1027
+ var body: some View {
1028
+ if entry.items.isEmpty {
1029
+ VStack(spacing: 8) {${w.showAppIcon
1030
+ ? `
1031
+ Image("AppIcon")
1032
+ .resizable()
1033
+ .frame(width: 40, height: 40)
1034
+ .cornerRadius(10)`
1035
+ : ""}
1036
+ Text(entry.fallbackTitle)
1037
+ .font(.headline)
1038
+ .foregroundColor(titleColor)
1039
+ Text(entry.fallbackSubtitle)
1040
+ .font(.caption)
1041
+ .foregroundColor(.secondary)
1042
+ }
1043
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
1044
+ .background(${bgColor})
1045
+ } else {
1046
+ GeometryReader { geometry in
1047
+ let padding: CGFloat = 16
1048
+ let availableWidth = geometry.size.width - (padding * 2)
1049
+ let availableHeight = geometry.size.height - (padding * 2)
1050
+
1051
+ VStack(spacing: 8) {
1052
+ let layout = entry.layout
1053
+ let totalSlots = layout.map { $0.ratios.count }.reduce(0, +)
1054
+ let items = Array(entry.items.prefix(totalSlots))
1055
+
1056
+ ForEach(0..<layout.count, id: \\.self) { rowIndex in
1057
+ let rowConfig = layout[rowIndex]
1058
+ let rowRatios = rowConfig.ratios
1059
+ let rowHeight = (availableHeight - CGFloat(layout.count - 1) * 8) / CGFloat(layout.count)
1060
+
1061
+ ${w.name}FlexRow(
1062
+ rowRatios: rowRatios,
1063
+ justifyContent: rowConfig.justifyContent,
1064
+ alignItems: rowConfig.alignItems,
1065
+ availableWidth: availableWidth,
1066
+ rowHeight: rowHeight,
1067
+ items: items,
1068
+ layout: layout,
1069
+ rowIndex: rowIndex,
1070
+ titleColor: titleColor,
1071
+ subtitleColor: subtitleColor,
1072
+ accentColor: accentColor
1073
+ )
1074
+ }
1075
+ }
1076
+ .padding(padding)
1077
+ }
1078
+ .background(${bgColor})
1079
+ }
1080
+ }
1081
+ }
1082
+
1083
+ // MARK: ${w.name} Flex Row
1084
+
1085
+ struct ${w.name}FlexRow: View {
1086
+ let rowRatios: [Double]
1087
+ let justifyContent: FlexJustifyContent
1088
+ let alignItems: FlexAlignItems
1089
+ let availableWidth: CGFloat
1090
+ let rowHeight: CGFloat
1091
+ let items: [WidgetListItem]
1092
+ let layout: [FlexRowLayout]
1093
+ let rowIndex: Int
1094
+ let titleColor: Color
1095
+ let subtitleColor: Color
1096
+ let accentColor: Color
1097
+
1098
+ private func getItemIndex(rowIndex: Int, colIndex: Int) -> Int {
1099
+ var index = 0
1100
+ for r in 0..<rowIndex {
1101
+ index += layout[r].ratios.count
1102
+ }
1103
+ return index + colIndex
1104
+ }
1105
+
1106
+ var body: some View {
1107
+ let itemCount = rowRatios.count
1108
+ let defaultSpacing: CGFloat = 8
1109
+
1110
+ // For space-* modes, scale down cells to leave room for visible spacing
1111
+ // Use 60% of width for content, 40% for spacing distribution
1112
+ let useSpaceMode = justifyContent == .spaceBetween || justifyContent == .spaceAround || justifyContent == .spaceEvenly
1113
+ let contentFraction: CGFloat = useSpaceMode ? 0.6 : 1.0
1114
+
1115
+ // Calculate cell widths
1116
+ let baseContentWidth = availableWidth - CGFloat(itemCount - 1) * defaultSpacing
1117
+ let scaledContentWidth = baseContentWidth * contentFraction
1118
+ let cellWidths = rowRatios.map { CGFloat($0) * scaledContentWidth }
1119
+ let totalCellWidth = cellWidths.reduce(0, +)
1120
+
1121
+ HStack(spacing: 0) {
1122
+ switch justifyContent {
1123
+ case .flexStart:
1124
+ rowContent(cellWidths: cellWidths, spacing: defaultSpacing)
1125
+ Spacer(minLength: 0)
1126
+
1127
+ case .flexEnd:
1128
+ Spacer(minLength: 0)
1129
+ rowContent(cellWidths: cellWidths, spacing: defaultSpacing)
1130
+
1131
+ case .center:
1132
+ Spacer(minLength: 0)
1133
+ rowContent(cellWidths: cellWidths, spacing: defaultSpacing)
1134
+ Spacer(minLength: 0)
1135
+
1136
+ case .spaceBetween:
1137
+ if itemCount > 1 {
1138
+ let betweenSpacing = (availableWidth - totalCellWidth) / CGFloat(itemCount - 1)
1139
+ rowContentWithSpacing(cellWidths: cellWidths, spacing: betweenSpacing)
1140
+ } else {
1141
+ Spacer(minLength: 0)
1142
+ rowContent(cellWidths: cellWidths, spacing: defaultSpacing)
1143
+ Spacer(minLength: 0)
1144
+ }
1145
+
1146
+ case .spaceAround:
1147
+ let aroundSpacing = (availableWidth - totalCellWidth) / CGFloat(itemCount * 2)
1148
+ Spacer().frame(width: aroundSpacing)
1149
+ rowContentWithSpacing(cellWidths: cellWidths, spacing: aroundSpacing * 2)
1150
+ Spacer().frame(width: aroundSpacing)
1151
+
1152
+ case .spaceEvenly:
1153
+ let evenSpacing = (availableWidth - totalCellWidth) / CGFloat(itemCount + 1)
1154
+ Spacer().frame(width: evenSpacing)
1155
+ rowContentWithSpacing(cellWidths: cellWidths, spacing: evenSpacing)
1156
+ Spacer().frame(width: evenSpacing)
1157
+ }
1158
+ }
1159
+ .frame(width: availableWidth, height: rowHeight)
1160
+ }
1161
+
1162
+ @ViewBuilder
1163
+ private func rowContent(cellWidths: [CGFloat], spacing: CGFloat) -> some View {
1164
+ HStack(spacing: spacing) {
1165
+ ForEach(0..<rowRatios.count, id: \\.self) { colIndex in
1166
+ cellView(colIndex: colIndex, width: cellWidths[colIndex])
1167
+ }
1168
+ }
1169
+ }
1170
+
1171
+ @ViewBuilder
1172
+ private func rowContentWithSpacing(cellWidths: [CGFloat], spacing: CGFloat) -> some View {
1173
+ ForEach(0..<rowRatios.count, id: \\.self) { colIndex in
1174
+ if colIndex > 0 {
1175
+ Spacer().frame(width: spacing)
1176
+ }
1177
+ cellView(colIndex: colIndex, width: cellWidths[colIndex])
1178
+ }
1179
+ }
1180
+
1181
+ @ViewBuilder
1182
+ private func cellView(colIndex: Int, width: CGFloat) -> some View {
1183
+ let index = getItemIndex(rowIndex: rowIndex, colIndex: colIndex)
1184
+ let verticalAlignment: Alignment = {
1185
+ switch alignItems {
1186
+ case .flexStart: return .top
1187
+ case .flexEnd: return .bottom
1188
+ case .center, .stretch: return .center
1189
+ }
1190
+ }()
1191
+
1192
+ if index < items.count {
1193
+ let item = items[index]
1194
+ if let deepLink = item.deepLink, let url = URL(string: deepLink) {
1195
+ Link(destination: url) {
1196
+ ${w.name}FlexCell(
1197
+ item: item,
1198
+ defaultTitleColor: titleColor,
1199
+ defaultDescColor: subtitleColor,
1200
+ defaultAccentColor: accentColor
1201
+ )
1202
+ .frame(width: width, height: alignItems == .stretch ? rowHeight : nil, alignment: verticalAlignment)
1203
+ }
1204
+ } else {
1205
+ ${w.name}FlexCell(
1206
+ item: item,
1207
+ defaultTitleColor: titleColor,
1208
+ defaultDescColor: subtitleColor,
1209
+ defaultAccentColor: accentColor
1210
+ )
1211
+ .frame(width: width, height: alignItems == .stretch ? rowHeight : nil, alignment: verticalAlignment)
1212
+ }
1213
+ } else {
1214
+ Color.clear.frame(width: width, height: rowHeight)
1215
+ }
1216
+ }
1217
+ }
1218
+
751
1219
  struct ${w.name}: Widget {
752
1220
  let kind: String = "${w.name}"
753
1221
  ${deepLinkLine}
@@ -1008,10 +1476,12 @@ function generateWidgetSwiftCode(props, bundleId) {
1008
1476
  .map((w) => generateSingleWidgetCode(w))
1009
1477
  .join("\n");
1010
1478
  const bundleBody = props.widgets.map((w) => ` ${w.name}()`).join("\n");
1011
- // Include Color hex extension if any widget uses style or is list/single type
1012
- // (list/single items can have per-item colors set at runtime)
1479
+ // Include Color hex extension if any widget uses style or is list/single/flex-grid type
1480
+ // (list/single/flex-grid items can have per-item colors set at runtime)
1013
1481
  const needsColorExt = props.widgets.some((w) => w.type === "list" ||
1014
1482
  w.type === "single" ||
1483
+ w.type === "flex-grid" ||
1484
+ w.type === "grid" ||
1015
1485
  w.style?.backgroundColor ||
1016
1486
  w.style?.titleColor ||
1017
1487
  w.style?.subtitleColor ||
@@ -1043,7 +1513,7 @@ extension Color {
1043
1513
  `
1044
1514
  : "";
1045
1515
  // Determine if we need UIKit import (for UIImage check in icon rendering)
1046
- const needsUIKit = props.widgets.some((w) => w.type === "list" || w.type === "single");
1516
+ const needsUIKit = props.widgets.some((w) => w.type === "list" || w.type === "single" || w.type === "flex-grid" || w.type === "grid");
1047
1517
  const uiKitImport = needsUIKit ? "\nimport UIKit" : "";
1048
1518
  return `//
1049
1519
  // Widgets.swift
@@ -1531,6 +2001,7 @@ function resolveIosProps(props, appName, bundleIdentifier) {
1531
2001
  image: resolvedImage,
1532
2002
  icons: w.icons,
1533
2003
  gridLayout: w.gridLayout,
2004
+ flexLayout: w.flexLayout,
1534
2005
  style: w.style,
1535
2006
  fallbackTitle: w.fallbackTitle || w.displayName,
1536
2007
  fallbackSubtitle: w.fallbackSubtitle || "Open app to start",