@wix/ditto-codegen-public 1.0.180 → 1.0.182

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ import React, { type FC } from 'react';
2
+ import { inputs } from '@wix/editor';
3
+ import { FormField, Box, FillPreview, SidePanel } from '@wix/design-system';
4
+
5
+ interface ColorPickerFieldProps {
6
+ label: string;
7
+ value: string;
8
+ onChange: (value: string) => void;
9
+ }
10
+
11
+ export const ColorPickerField: FC<ColorPickerFieldProps> = ({
12
+ label,
13
+ value,
14
+ onChange,
15
+ }) => (
16
+ <SidePanel.Field>
17
+ <FormField label={label}>
18
+ <Box width="30px" height="30px">
19
+ <FillPreview
20
+ fill={value}
21
+ onClick={() => inputs.selectColor(value, { onChange })}
22
+ />
23
+ </Box>
24
+ </FormField>
25
+ </SidePanel.Field>
26
+ );
27
+
@@ -0,0 +1,34 @@
1
+ import React, { type FC } from 'react';
2
+ import { inputs } from '@wix/editor';
3
+ import { FormField, Button, Text, SidePanel } from '@wix/design-system';
4
+
5
+ interface FontValue {
6
+ font: string;
7
+ textDecoration: string;
8
+ }
9
+
10
+ interface FontPickerFieldProps {
11
+ label: string;
12
+ value: FontValue;
13
+ onChange: (value: FontValue) => void;
14
+ }
15
+
16
+ export const FontPickerField: FC<FontPickerFieldProps> = ({
17
+ label,
18
+ value,
19
+ onChange,
20
+ }) => (
21
+ <SidePanel.Field>
22
+ <FormField label={label}>
23
+ <Button
24
+ size="small"
25
+ priority="secondary"
26
+ onClick={() => inputs.selectFont(value, { onChange })}
27
+ fullWidth
28
+ >
29
+ <Text size="small" ellipsis>Change Font</Text>
30
+ </Button>
31
+ </FormField>
32
+ </SidePanel.Field>
33
+ );
34
+
@@ -0,0 +1,10 @@
1
+ import React, { type FC } from 'react';
2
+
3
+ interface SeparatorProps {
4
+ style: React.CSSProperties;
5
+ }
6
+
7
+ export const Separator: FC<SeparatorProps> = ({ style }) => (
8
+ <span style={style}>:</span>
9
+ );
10
+
@@ -0,0 +1,23 @@
1
+ import React, { type FC } from 'react';
2
+
3
+ interface TimeBlockProps {
4
+ value: string;
5
+ label: string;
6
+ numberStyle: React.CSSProperties;
7
+ labelStyle: React.CSSProperties;
8
+ blockStyle: React.CSSProperties;
9
+ }
10
+
11
+ export const TimeBlock: FC<TimeBlockProps> = ({
12
+ value,
13
+ label,
14
+ numberStyle,
15
+ labelStyle,
16
+ blockStyle,
17
+ }) => (
18
+ <div style={blockStyle}>
19
+ <div style={numberStyle}>{value}</div>
20
+ <div style={labelStyle}>{label}</div>
21
+ </div>
22
+ );
23
+
@@ -0,0 +1,18 @@
1
+ import { extensions } from '@wix/astro/builders';
2
+ export const sitewidgetcountdownWidget = extensions.customElement({
3
+ id: '54db089e-aa5b-436a-9dfa-074f2efad2ce',
4
+ name: 'Countdown Widget',
5
+ tagName: 'countdown-widget',
6
+ element: './site/widgets/custom-elements/countdown-widget/widget.tsx',
7
+ settings: './site/widgets/custom-elements/countdown-widget/panel.tsx',
8
+ installation: {
9
+ autoAdd: true
10
+ },
11
+ width: {
12
+ defaultWidth: 500,
13
+ allowStretch: true
14
+ },
15
+ height: {
16
+ defaultHeight: 500
17
+ }
18
+ })
@@ -0,0 +1,146 @@
1
+ import React, { type FC, useState, useEffect, useCallback } from "react";
2
+ import { widget } from "@wix/editor";
3
+ import {
4
+ SidePanel,
5
+ WixDesignSystemProvider,
6
+ Input,
7
+ FormField,
8
+ TimeInput,
9
+ Box,
10
+ } from "@wix/design-system";
11
+ import "@wix/design-system/styles.global.css";
12
+ import { ColorPickerField } from "./components/ColorPickerField";
13
+ import { FontPickerField } from "./components/FontPickerField";
14
+ import { parseTimeValue } from "./utils";
15
+
16
+ const DEFAULT_BG_COLOR = "#0a0e27";
17
+ const DEFAULT_TEXT_COLOR = "#00ff88";
18
+ const DEFAULT_TEXT_FONT = "";
19
+ const DEFAULT_TEXT_DECORATION = "";
20
+
21
+ const Panel: FC = () => {
22
+ const [title, setTitle] = useState<string>("Countdown");
23
+ const [targetDate, setTargetDate] = useState<string>("");
24
+ const [targetTime, setTargetTime] = useState<string>("00:00");
25
+ const [bgColor, setBgColor] = useState<string>(DEFAULT_BG_COLOR);
26
+ const [textColor, setTextColor] = useState<string>(DEFAULT_TEXT_COLOR);
27
+ const [font, setFont] = useState({ font: DEFAULT_TEXT_FONT, textDecoration: DEFAULT_TEXT_DECORATION });
28
+
29
+ useEffect(() => {
30
+ Promise.all([
31
+ widget.getProp("title"),
32
+ widget.getProp("target-date"),
33
+ widget.getProp("target-time"),
34
+ widget.getProp("bg-color"),
35
+ widget.getProp("text-color"),
36
+ widget.getProp("font"),
37
+ ])
38
+ .then(([titleVal, dateVal, timeVal, bgColorVal, textColorVal, fontString]) => {
39
+ setTitle(titleVal || "Countdown");
40
+ setTargetDate(dateVal || "");
41
+ setTargetTime(timeVal || "00:00");
42
+ setBgColor(bgColorVal || DEFAULT_BG_COLOR);
43
+ setTextColor(textColorVal || DEFAULT_TEXT_COLOR);
44
+ setFont(JSON.parse(fontString || "{}"));
45
+ })
46
+ .catch((error) => console.error("Failed to fetch widget properties:", error));
47
+ }, []);
48
+
49
+ const handleTitleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
50
+ const newTitle = event.target.value;
51
+ setTitle(newTitle);
52
+ widget.setProp("title", newTitle);
53
+ }, []);
54
+
55
+ const handleDateChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
56
+ const newDate = event.target.value;
57
+ setTargetDate(newDate);
58
+ widget.setProp("target-date", newDate);
59
+ }, []);
60
+
61
+ const handleTimeChange = useCallback(({ date }: { date: Date }) => {
62
+ if (date) {
63
+ const hours = String(date.getHours()).padStart(2, '0');
64
+ const minutes = String(date.getMinutes()).padStart(2, '0');
65
+ const newTime = `${hours}:${minutes}`;
66
+ setTargetTime(newTime);
67
+ widget.setProp("target-time", newTime);
68
+ }
69
+ }, []);
70
+
71
+ const handleBgColorChange = (value: string) => {
72
+ setBgColor(value);
73
+ widget.setProp("bg-color", value);
74
+ };
75
+
76
+ const handleTextColorChange = (value: string) => {
77
+ setTextColor(value);
78
+ widget.setProp("text-color", value);
79
+ };
80
+
81
+ const handleFontChange = (value: { font: string; textDecoration: string }) => {
82
+ setFont(value);
83
+ widget.setProp("font", JSON.stringify(value));
84
+ };
85
+
86
+ return (
87
+ <WixDesignSystemProvider>
88
+ <SidePanel width="300" height="100vh">
89
+ <SidePanel.Header title="Countdown Settings" />
90
+ <SidePanel.Content noPadding stretchVertically>
91
+ <Box direction="vertical" gap="24px">
92
+ <SidePanel.Field>
93
+ <FormField label="Title" required>
94
+ <Input
95
+ type="text"
96
+ value={title}
97
+ onChange={handleTitleChange}
98
+ placeholder="Enter countdown title"
99
+ />
100
+ </FormField>
101
+ </SidePanel.Field>
102
+
103
+ <SidePanel.Field>
104
+ <FormField label="Target Date" required>
105
+ <Input
106
+ type="date"
107
+ value={targetDate}
108
+ onChange={handleDateChange}
109
+ />
110
+ </FormField>
111
+ </SidePanel.Field>
112
+
113
+ <SidePanel.Field>
114
+ <FormField label="Target Time" required>
115
+ <TimeInput
116
+ value={parseTimeValue(targetTime)}
117
+ onChange={handleTimeChange}
118
+ />
119
+ </FormField>
120
+ </SidePanel.Field>
121
+
122
+ <ColorPickerField
123
+ label="Background Color"
124
+ value={bgColor}
125
+ onChange={handleBgColorChange}
126
+ />
127
+
128
+ <ColorPickerField
129
+ label="Text Color"
130
+ value={textColor}
131
+ onChange={handleTextColorChange}
132
+ />
133
+
134
+ <FontPickerField
135
+ label="Text Font"
136
+ value={font}
137
+ onChange={handleFontChange}
138
+ />
139
+ </Box>
140
+ </SidePanel.Content>
141
+ </SidePanel>
142
+ </WixDesignSystemProvider>
143
+ );
144
+ };
145
+
146
+ export default Panel;
@@ -0,0 +1,73 @@
1
+ import type React from 'react';
2
+
3
+ interface StyleProps {
4
+ bgColor: string;
5
+ textColor: string;
6
+ textFont?: string;
7
+ textDecoration?: string;
8
+ }
9
+
10
+ export interface WidgetStyles {
11
+ wrapper: React.CSSProperties;
12
+ title: React.CSSProperties;
13
+ digits: React.CSSProperties;
14
+ block: React.CSSProperties;
15
+ number: React.CSSProperties;
16
+ label: React.CSSProperties;
17
+ separator: React.CSSProperties;
18
+ message: React.CSSProperties;
19
+ }
20
+
21
+ export function createStyles({ bgColor, textColor, textFont, textDecoration }: StyleProps): WidgetStyles {
22
+ return {
23
+ wrapper: {
24
+ backgroundColor: bgColor,
25
+ padding: '24px 32px',
26
+ textAlign: 'center',
27
+ display: 'inline-block',
28
+ },
29
+ title: {
30
+ font: textFont || '600 24px sans-serif',
31
+ color: textColor,
32
+ textDecoration,
33
+ marginBottom: '16px',
34
+ },
35
+ digits: {
36
+ display: 'flex',
37
+ alignItems: 'center',
38
+ justifyContent: 'center',
39
+ gap: '16px',
40
+ },
41
+ block: {
42
+ display: 'flex',
43
+ flexDirection: 'column',
44
+ alignItems: 'center',
45
+ padding: '12px 16px',
46
+ },
47
+ number: {
48
+ font: textFont || '600 40px sans-serif',
49
+ color: textColor,
50
+ textDecoration,
51
+ lineHeight: 1,
52
+ },
53
+ label: {
54
+ fontSize: '12px',
55
+ marginTop: '8px',
56
+ color: textColor,
57
+ opacity: 0.7,
58
+ },
59
+ separator: {
60
+ font: textFont || '600 32px sans-serif',
61
+ color: textColor,
62
+ textDecoration,
63
+ lineHeight: 1,
64
+ opacity: 0.5,
65
+ marginBottom: '20px',
66
+ },
67
+ message: {
68
+ fontSize: '18px',
69
+ color: textColor,
70
+ },
71
+ };
72
+ }
73
+
@@ -0,0 +1,46 @@
1
+ export interface TimeRemaining {
2
+ days: number;
3
+ hours: number;
4
+ minutes: number;
5
+ seconds: number;
6
+ isExpired: boolean;
7
+ }
8
+
9
+ export const INITIAL_TIME: TimeRemaining = {
10
+ days: 0,
11
+ hours: 0,
12
+ minutes: 0,
13
+ seconds: 0,
14
+ isExpired: false,
15
+ };
16
+
17
+ export function calculateTimeRemaining(targetDate: string, targetTime: string): TimeRemaining {
18
+ const target = new Date(`${targetDate}T${targetTime}`);
19
+ const diff = target.getTime() - Date.now();
20
+
21
+ if (diff <= 0) {
22
+ return { ...INITIAL_TIME, isExpired: true };
23
+ }
24
+
25
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
26
+ const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
27
+ const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
28
+ const seconds = Math.floor((diff % (1000 * 60)) / 1000);
29
+
30
+ return { days, hours, minutes, seconds, isExpired: false };
31
+ }
32
+
33
+ export function padNumber(value: number): string {
34
+ return String(value).padStart(2, '0');
35
+ }
36
+
37
+ export function parseTimeValue(timeString: string): Date {
38
+ const [hours, minutes] = timeString.split(':').map(Number);
39
+ const date = new Date();
40
+ date.setHours(hours || 0);
41
+ date.setMinutes(minutes || 0);
42
+ date.setSeconds(0);
43
+ date.setMilliseconds(0);
44
+ return date;
45
+ }
46
+
@@ -0,0 +1,97 @@
1
+ import React, { type FC, useState, useEffect } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import reactToWebComponent from 'react-to-webcomponent';
4
+ import { TimeBlock } from './components/TimeBlock';
5
+ import { Separator } from './components/Separator';
6
+ import { calculateTimeRemaining, padNumber, INITIAL_TIME, type TimeRemaining } from './utils';
7
+ import { createStyles } from './styles';
8
+
9
+ interface WidgetProps {
10
+ title?: string;
11
+ targetDate?: string;
12
+ targetTime?: string;
13
+ bgColor?: string;
14
+ textColor?: string;
15
+ font?: string;
16
+ }
17
+
18
+ const CustomElement: FC<WidgetProps> = ({
19
+ title = 'Countdown',
20
+ targetDate = '',
21
+ targetTime = '00:00',
22
+ bgColor = '#ffffff',
23
+ textColor = '#333333',
24
+ font = "{}",
25
+ }) => {
26
+ const { font: textFont, textDecoration } = JSON.parse(font);
27
+ const [time, setTime] = useState<TimeRemaining>(INITIAL_TIME);
28
+ const styles = createStyles({ bgColor, textColor, textFont, textDecoration });
29
+
30
+ useEffect(() => {
31
+ if (!targetDate) return;
32
+
33
+ const update = () => setTime(calculateTimeRemaining(targetDate, targetTime));
34
+ update();
35
+
36
+ const interval = setInterval(update, 1000);
37
+ return () => clearInterval(interval);
38
+ }, [targetDate, targetTime]);
39
+
40
+ if (!targetDate) {
41
+ return (
42
+ <div style={styles.wrapper}>
43
+ {title && <div style={styles.title}>{title}</div>}
44
+ <div style={styles.message}>Set a target date</div>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ if (time.isExpired) {
50
+ return (
51
+ <div style={styles.wrapper}>
52
+ {title && <div style={styles.title}>{title}</div>}
53
+ <div style={styles.message}>Event has started!</div>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ const timeUnits = [
59
+ { value: time.days, label: 'Days' },
60
+ { value: time.hours, label: 'Hours' },
61
+ { value: time.minutes, label: 'Minutes' },
62
+ { value: time.seconds, label: 'Seconds' },
63
+ ];
64
+
65
+ return (
66
+ <div style={styles.wrapper}>
67
+ {title && <div style={styles.title}>{title}</div>}
68
+ <div style={styles.digits}>
69
+ {timeUnits.map((unit, index) => (
70
+ <React.Fragment key={unit.label}>
71
+ {index > 0 && <Separator style={styles.separator} />}
72
+ <TimeBlock
73
+ value={padNumber(unit.value)}
74
+ label={unit.label}
75
+ numberStyle={styles.number}
76
+ labelStyle={styles.label}
77
+ blockStyle={styles.block}
78
+ />
79
+ </React.Fragment>
80
+ ))}
81
+ </div>
82
+ </div>
83
+ );
84
+ };
85
+
86
+ const customElement = reactToWebComponent(CustomElement, React, ReactDOM, {
87
+ props: {
88
+ title: 'string',
89
+ targetDate: 'string',
90
+ targetTime: 'string',
91
+ bgColor: 'string',
92
+ textColor: 'string',
93
+ font: 'string',
94
+ },
95
+ });
96
+
97
+ export default customElement;
package/dist/out.js CHANGED
@@ -36567,6 +36567,12 @@ var require_CodegenAIProxyService = __commonJS({
36567
36567
  maxRetries: 3,
36568
36568
  temperature: 0
36569
36569
  });
36570
+ if (response.finishReason === "length") {
36571
+ throw new ditto_codegen_types_12.GenerateObjectError(`Output truncated due to max output tokens limit. Agent: ${payload.agentName}`, {
36572
+ status: 413,
36573
+ statusText: "Output truncated - max tokens reached"
36574
+ });
36575
+ }
36570
36576
  return response;
36571
36577
  };
36572
36578
  }
@@ -37449,7 +37455,23 @@ When the blueprint includes EMBEDDED_SCRIPT extensions:
37449
37455
  * User-generated content (reviews, comments, submissions)
37450
37456
  * Event logs (if explicitly requested for analytics/tracking purposes)
37451
37457
  * Multi-record relational data that is NOT configuration
37452
- - If in doubt whether something is "configuration" or "data": If it controls HOW the embedded script works or looks, it's configuration \u2192 use embedded script parameters`;
37458
+ - If in doubt whether something is "configuration" or "data": If it controls HOW the embedded script works or looks, it's configuration \u2192 use embedded script parameters
37459
+
37460
+ CRITICAL - SITE WIDGET SETTINGS vs CMS COLLECTIONS:
37461
+ When the blueprint includes SITE_WIDGET extensions:
37462
+ - SITE_WIDGET has a built-in settings panel (panel.tsx) that handles ALL widget configuration - dates, titles, colors, numbers, toggles, text, etc.
37463
+ - For SITE_WIDGET-ONLY blueprints (no DASHBOARD_PAGE or other extensions): return collections: [] - the widget panel handles everything
37464
+ - NEVER create collections to store widget configuration data - this is ALWAYS handled by the widget's settings panel
37465
+ - Only create collections for SITE_WIDGET when ALL of these conditions are met:
37466
+ * The blueprint ALSO includes a DASHBOARD_PAGE extension that needs to manage data the widget displays
37467
+ * OR the widget explicitly needs to display MULTIPLE records of user-generated/business data (not configuration)
37468
+ * AND the data is NOT configuration (dates, colors, settings, display options are ALWAYS configuration)
37469
+ - Examples of what is CONFIGURATION (use widget panel, NOT collections):
37470
+ * Target date/time for countdown \u2192 widget panel
37471
+ * Display colors, fonts, sizes \u2192 widget panel
37472
+ * Headlines, labels, messages \u2192 widget panel
37473
+ * Feature toggles, show/hide options \u2192 widget panel
37474
+ * Numeric settings (delays, intervals, thresholds) \u2192 widget panel`;
37453
37475
  var cmsPlannerExample = `Blueprint indicates a handling fees system for products.
37454
37476
 
37455
37477
  For the collections field, return:
@@ -37470,6 +37492,12 @@ For the collections field, return:
37470
37492
  - Example (WRONG):
37471
37493
  * Creating both embeddedScriptParameters AND a "popup-configurations" CMS collection - this is duplicate storage
37472
37494
  - Only create CMS collections if there's genuinely new data needed beyond what's in embeddedScriptParameters (e.g., user-generated content, event logs if explicitly requested, business entities)
37495
+
37496
+ Avoid Duplicate Data Storage - SITE WIDGET CONFIGURATION:
37497
+ - Site widgets have a built-in settings panel (panel.tsx) - ALL widget configuration goes there
37498
+ - For SITE_WIDGET-ONLY blueprints: ALWAYS return collections: [] (empty array)
37499
+ - The widget panel handles: dates, times, colors, text, numbers, toggles, display settings, countdown targets, etc.
37500
+ - ONLY create collections for site widgets when there's a DASHBOARD_PAGE that manages multi-record data the widget displays
37473
37501
  5) Initial Data Assessment:
37474
37502
  - After defining the collection schema, evaluate if initial/seed data can be implied from the blueprint
37475
37503
  - Consider if the blueprint describes example data, default configurations, sample content, or starter records that would help demonstrate the app's functionality
@@ -64449,6 +64477,7 @@ var require_dashboard_page_instructions = __commonJS({
64449
64477
  "Page.Section",
64450
64478
  "RichTextInputArea",
64451
64479
  "SectionHeader",
64480
+ "SidePanel",
64452
64481
  "Table",
64453
64482
  "TableActionCell",
64454
64483
  "TableListHeader",
@@ -64598,7 +64627,14 @@ var require_load_examples = __commonJS({
64598
64627
  description: "A Custom Element that displays a countdown timer",
64599
64628
  files: {
64600
64629
  [types_1.ExtensionType.SITE_WIDGET]: [
64601
- "custom-element/src/widgets/custom-elements/countdown-timer/widget.tsx"
64630
+ "custom-element/countdown-widget/components/ColorPickerField.tsx",
64631
+ "custom-element/countdown-widget/components/FontPickerField.tsx",
64632
+ "custom-element/countdown-widget/components/Separator.tsx",
64633
+ "custom-element/countdown-widget/components/TimeBlock.tsx",
64634
+ "custom-element/countdown-widget/panel.tsx",
64635
+ "custom-element/countdown-widget/styles.ts",
64636
+ "custom-element/countdown-widget/utils.ts",
64637
+ "custom-element/countdown-widget/widget.tsx"
64602
64638
  ]
64603
64639
  }
64604
64640
  },
@@ -65791,18 +65827,39 @@ var require_custom_element_instructions = __commonJS({
65791
65827
  Object.defineProperty(exports2, "__esModule", { value: true });
65792
65828
  exports2.customElementInstructions = void 0;
65793
65829
  var typescript_quality_guidelines_1 = require_typescript_quality_guidelines();
65830
+ var wdsPackage_1 = require_wdsPackage();
65831
+ var dashboard_page_instructions_1 = require_dashboard_page_instructions();
65794
65832
  var customElementRole = `You are a senior Wix CLI App Developer. Your job is to produce a beautiful, production\u2011quality site component quickly and correctly.`;
65795
65833
  var customElementImplementationGuidelines = `<rules>
65796
65834
  - Return ONLY a JSON object matching the schema { path, content, type } (no prose, no markdown)
65797
65835
  - type must be "typescript"; content must be the COMPLETE file source
65798
- - Do NOT add dependencies; do NOT use @wix/design-system or @wix/wix-ui-icons-common
65799
65836
  - Do NOT invent types/modules/props; use only what exists in the scaffold and standard libs
65800
65837
  </rules>
65801
65838
 
65839
+ <architecture>
65840
+ Site widgets consist of two files:
65841
+
65842
+ 1. **widget.tsx** - React component converted to Web Component
65843
+ - Import React, ReactDOM, and react-to-webcomponent
65844
+ - Define a Props interface for configurable properties (camelCase)
65845
+ - Create a React FC that renders the widget UI
65846
+ - Convert to web component using reactToWebComponent with props mapping
65847
+ - Props in reactToWebComponent config use camelCase keys with 'string' type
65848
+ - Use inline styles for styling
65849
+
65850
+ 2. **panel.tsx** - Settings panel shown in the Wix Editor
65851
+ - Import React hooks, @wix/editor widget API, and @wix/design-system components
65852
+ - Import '@wix/design-system/styles.global.css' for design system styles
65853
+ - Use useState for each configurable property
65854
+ - Use useEffect to load initial values via widget.getProp('kebab-case-name')
65855
+ - Create onChange handlers that update local state AND call widget.setProp('kebab-case-name', value)
65856
+ - Wrap content in WixDesignSystemProvider > SidePanel > SidePanel.Content
65857
+ - Use ONLY WDS components listed in the <wds_reference> section
65858
+ </architecture>
65859
+
65802
65860
  <engineering_guidelines>
65803
65861
  - Implement a TypeScript custom element with proper typing and clear interfaces
65804
- - Keep the component small, pure, and readable; remove dead code/unused imports
65805
- - Name things clearly; avoid magic numbers; extract tiny helpers if it improves clarity
65862
+ - Ensure prop names are consistent between widget.tsx (camelCase) and panel.tsx (kebab-case)
65806
65863
  </engineering_guidelines>
65807
65864
 
65808
65865
  ${typescript_quality_guidelines_1.typescriptQualityGuidelines}
@@ -65837,7 +65894,8 @@ var require_custom_element_instructions = __commonJS({
65837
65894
  exports2.customElementInstructions = {
65838
65895
  role: customElementRole,
65839
65896
  implementationGuidelines: customElementImplementationGuidelines,
65840
- implementationRequirements: customElementImplementationRequirements
65897
+ implementationRequirements: customElementImplementationRequirements,
65898
+ wdsReference: () => (0, wdsPackage_1.buildWdsSystemPrompt)(dashboard_page_instructions_1.supportedWDSComponents)
65841
65899
  };
65842
65900
  }
65843
65901
  });
@@ -65857,7 +65915,7 @@ var require_custom_element_prompt = __commonJS({
65857
65915
  apiNames,
65858
65916
  useData,
65859
65917
  useIteration
65860
- }).withRole(custom_element_instructions_1.customElementInstructions.role).withContextExplanation().withCorePrinciples().withImplementationGuidelines(custom_element_instructions_1.customElementInstructions.implementationGuidelines).withApiDocs().withExamples();
65918
+ }).withRole(custom_element_instructions_1.customElementInstructions.role).withContextExplanation().withCorePrinciples().withImplementationGuidelines(custom_element_instructions_1.customElementInstructions.implementationGuidelines).withSection("wds_reference", custom_element_instructions_1.customElementInstructions.wdsReference()).withApiDocs().withExamples();
65861
65919
  if (useData) {
65862
65920
  systemPrompt.withSection("implementation_requirements", custom_element_instructions_1.customElementInstructions.implementationRequirements);
65863
65921
  }
@@ -78557,11 +78615,13 @@ var require_extensionGenerators = __commonJS({
78557
78615
  static createCustomElementData(id, name, scaffoldDir) {
78558
78616
  const kebabCaseComponentName = (0, ditto_scaffolding_2.toKebabCase)(name);
78559
78617
  const componentPath = getScaffoldPath(scaffoldDir, "widget.tsx");
78618
+ const settingsPath = getScaffoldPath(scaffoldDir, "panel.tsx");
78560
78619
  return {
78561
78620
  id,
78562
78621
  name,
78563
78622
  tagName: kebabCaseComponentName,
78564
78623
  element: componentPath,
78624
+ settings: settingsPath,
78565
78625
  installation: { autoAdd: true },
78566
78626
  width: { defaultWidth: 500, allowStretch: true },
78567
78627
  height: { defaultHeight: 500 }
@@ -0,0 +1,53 @@
1
+ import React, { type FC, useState, useEffect, useCallback } from "react";
2
+ import { widget } from "@wix/editor";
3
+ import {
4
+ SidePanel,
5
+ WixDesignSystemProvider,
6
+ Input,
7
+ FormField,
8
+ } from "@wix/design-system";
9
+ import "@wix/design-system/styles.global.css";
10
+
11
+ const Panel: FC = () => {
12
+ const [displayName, setDisplayName] = useState<string>("");
13
+
14
+ useEffect(() => {
15
+ widget
16
+ .getProp("display-name")
17
+ .then((displayName) =>
18
+ setDisplayName(displayName || `Your Widget's Title`),
19
+ )
20
+ .catch((error) => console.error("Failed to fetch display-name:", error));
21
+ }, [setDisplayName]);
22
+
23
+ const handleDisplayNameChange = useCallback(
24
+ (event: React.ChangeEvent<HTMLInputElement>) => {
25
+ const newDisplayName = event.target.value;
26
+ setDisplayName(newDisplayName);
27
+ widget.setProp("display-name", newDisplayName);
28
+ },
29
+ [setDisplayName],
30
+ );
31
+
32
+ return (
33
+ <WixDesignSystemProvider>
34
+ <SidePanel width="300" height="100vh">
35
+ <SidePanel.Content noPadding stretchVertically>
36
+ <SidePanel.Field>
37
+ <FormField label="Display Name">
38
+ <Input
39
+ type="text"
40
+ value={displayName}
41
+ onChange={handleDisplayNameChange}
42
+ aria-label="Display Name"
43
+ />
44
+ </FormField>
45
+ </SidePanel.Field>
46
+ </SidePanel.Content>
47
+ </SidePanel>
48
+ </WixDesignSystemProvider>
49
+ );
50
+ };
51
+
52
+ export default Panel;
53
+
@@ -1,5 +1,34 @@
1
- export default class extends HTMLElement {
2
- public connectedCallback() {
3
- this.innerHTML = `<div data-testid="custom-element">Hello from custom element!</div>`;
1
+ import React, { type FC } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import reactToWebComponent from 'react-to-webcomponent';
4
+
5
+ interface Props {
6
+ displayName?: string;
7
+ }
8
+
9
+ const CustomElement: FC<Props> = ({
10
+ displayName = `Your Widget's Title`,
11
+ }) => {
12
+ return (
13
+ <div>
14
+ <h2>{displayName}</h2>
15
+ <hr />
16
+ <p>
17
+ This is a Site Widget generated by Wix CLI.<br />
18
+ </p>
19
+ </div>
20
+ );
21
+ };
22
+
23
+ const customElement = reactToWebComponent(
24
+ CustomElement,
25
+ React,
26
+ ReactDOM as any,
27
+ {
28
+ props: {
29
+ displayName: 'string',
30
+ },
4
31
  }
5
- }
32
+ );
33
+
34
+ export default customElement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/ditto-codegen-public",
3
- "version": "1.0.180",
3
+ "version": "1.0.182",
4
4
  "description": "AI-powered Wix CLI app generator - standalone executable",
5
5
  "scripts": {
6
6
  "build": "node build.mjs",
@@ -24,5 +24,5 @@
24
24
  "@wix/ditto-codegen": "1.0.0",
25
25
  "esbuild": "^0.25.9"
26
26
  },
27
- "falconPackageHash": "e8dba8e74db8a8a39dce94ec11ac6c277c9fa845178e72670a580574"
27
+ "falconPackageHash": "2d00f26181910908e52a2ac219529e18c316ff62684e562f64fec209"
28
28
  }
@@ -1,493 +0,0 @@
1
- import { items } from '@wix/data';
2
- import { window as wixWindow } from '@wix/site-window';
3
-
4
- interface CountdownConfig {
5
- _id: string;
6
- targetDate: Date;
7
- title?: string;
8
- isActive: boolean;
9
- }
10
-
11
- interface TimeRemaining {
12
- days: number;
13
- hours: number;
14
- minutes: number;
15
- seconds: number;
16
- }
17
-
18
- export default class extends HTMLElement {
19
- private intervalId: number | null = null;
20
- private config: CountdownConfig | null = null;
21
- private isEditorMode = false;
22
-
23
- public async connectedCallback(): Promise<void> {
24
- await this.checkEnvironment();
25
- if (this.isEditorMode) {
26
- this.renderEditorPlaceholder();
27
- } else {
28
- await this.loadConfig();
29
- }
30
- }
31
-
32
- public disconnectedCallback(): void {
33
- if (this.intervalId !== null) {
34
- clearInterval(this.intervalId);
35
- this.intervalId = null;
36
- }
37
- }
38
-
39
- private async checkEnvironment(): Promise<void> {
40
- try {
41
- const currentViewMode = await wixWindow.viewMode();
42
- this.isEditorMode = currentViewMode === 'Editor';
43
- } catch (error) {
44
- console.error('Failed to check view mode:', error);
45
- this.isEditorMode = false;
46
- }
47
- }
48
-
49
- private async loadConfig(): Promise<void> {
50
- try {
51
- const result = await items.query('countdown-config')
52
- .eq('isActive', true)
53
- .limit(1)
54
- .find();
55
-
56
- if (result.items.length > 0) {
57
- const item = result.items[0];
58
- this.config = {
59
- _id: item._id as string,
60
- targetDate: new Date(item.targetDate as string),
61
- title: item.title as string | undefined,
62
- isActive: item.isActive as boolean
63
- };
64
- this.render();
65
- this.startCountdown();
66
- } else {
67
- this.renderNoConfig();
68
- }
69
- } catch (error) {
70
- console.error('Failed to load countdown config:', error);
71
- this.renderError();
72
- }
73
- }
74
-
75
- private calculateTimeRemaining(targetDate: Date): TimeRemaining | null {
76
- const now = new Date().getTime();
77
- const target = targetDate.getTime();
78
- const difference = target - now;
79
-
80
- if (difference <= 0) {
81
- return null;
82
- }
83
-
84
- const days = Math.floor(difference / (1000 * 60 * 60 * 24));
85
- const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
86
- const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
87
- const seconds = Math.floor((difference % (1000 * 60)) / 1000);
88
-
89
- return { days, hours, minutes, seconds };
90
- }
91
-
92
- private startCountdown(): void {
93
- if (!this.config || !this.config.isActive) {
94
- return;
95
- }
96
-
97
- this.intervalId = window.setInterval(() => {
98
- this.updateCountdown();
99
- }, 1000);
100
- }
101
-
102
- private updateCountdown(): void {
103
- if (!this.config) {
104
- return;
105
- }
106
-
107
- const timeRemaining = this.calculateTimeRemaining(this.config.targetDate);
108
-
109
- if (timeRemaining === null) {
110
- this.renderExpired();
111
- if (this.intervalId !== null) {
112
- clearInterval(this.intervalId);
113
- this.intervalId = null;
114
- }
115
- return;
116
- }
117
-
118
- this.renderCountdown(timeRemaining);
119
- }
120
-
121
- private render(): void {
122
- if (!this.config) {
123
- return;
124
- }
125
-
126
- const timeRemaining = this.calculateTimeRemaining(this.config.targetDate);
127
-
128
- if (timeRemaining === null) {
129
- this.renderExpired();
130
- } else {
131
- this.renderCountdown(timeRemaining);
132
- }
133
- }
134
-
135
- private getStyles(): string {
136
- return `
137
- <style>
138
- /* Editor Placeholder Styles */
139
- .editor-container {
140
- font-family: 'Courier New', monospace;
141
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
142
- color: #00ff88;
143
- padding: 48px 24px;
144
- border-radius: 16px;
145
- text-align: center;
146
- box-shadow: 0 8px 32px rgba(0, 255, 136, 0.2);
147
- max-width: 600px;
148
- margin: 0 auto;
149
- border: 2px solid #00ff88;
150
- }
151
-
152
- .editor-icon {
153
- font-size: 64px;
154
- margin-bottom: 16px;
155
- animation: pulse 2s ease-in-out infinite;
156
- }
157
-
158
- .editor-title {
159
- font-size: 28px;
160
- font-weight: 700;
161
- margin: 0 0 12px 0;
162
- text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
163
- letter-spacing: 2px;
164
- }
165
-
166
- .editor-message {
167
- font-size: 16px;
168
- opacity: 0.85;
169
- margin: 0;
170
- line-height: 1.6;
171
- }
172
-
173
- /* Countdown Timer Styles */
174
- .timer-container {
175
- font-family: 'Orbitron', 'Rajdhani', 'Exo 2', monospace, sans-serif;
176
- background: linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%);
177
- color: #00d9ff;
178
- padding: 40px 24px;
179
- border-radius: 20px;
180
- text-align: center;
181
- box-shadow: 0 20px 60px rgba(0, 217, 255, 0.3), inset 0 0 40px rgba(0, 217, 255, 0.1);
182
- max-width: 700px;
183
- margin: 0 auto;
184
- position: relative;
185
- overflow: hidden;
186
- }
187
-
188
- .timer-glow {
189
- content: '';
190
- position: absolute;
191
- top: -50%;
192
- left: -50%;
193
- width: 200%;
194
- height: 200%;
195
- background: radial-gradient(circle, rgba(0, 217, 255, 0.1) 0%, transparent 70%);
196
- animation: rotate 10s linear infinite;
197
- }
198
-
199
- .timer-title {
200
- font-size: 32px;
201
- font-weight: 900;
202
- margin: 0 0 12px 0;
203
- text-shadow: 0 0 20px rgba(0, 217, 255, 0.8), 0 0 40px rgba(0, 217, 255, 0.4);
204
- letter-spacing: 3px;
205
- text-transform: uppercase;
206
- position: relative;
207
- z-index: 1;
208
- }
209
-
210
- .timer-grid {
211
- display: grid;
212
- grid-template-columns: repeat(4, 1fr);
213
- gap: 20px;
214
- margin: 32px 0;
215
- position: relative;
216
- z-index: 1;
217
- }
218
-
219
- .time-unit {
220
- background: rgba(0, 217, 255, 0.15);
221
- border-radius: 12px;
222
- padding: 24px 12px;
223
- backdrop-filter: blur(10px);
224
- border: 2px solid rgba(0, 217, 255, 0.3);
225
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), inset 0 0 20px rgba(0, 217, 255, 0.1);
226
- transition: all 0.3s ease;
227
- }
228
-
229
- .time-unit:hover {
230
- transform: translateY(-4px);
231
- border-color: rgba(0, 217, 255, 0.6);
232
- box-shadow: 0 12px 40px rgba(0, 217, 255, 0.4), inset 0 0 30px rgba(0, 217, 255, 0.2);
233
- }
234
-
235
- .time-number {
236
- font-size: 48px;
237
- font-weight: 900;
238
- margin: 0;
239
- text-shadow: 0 0 15px rgba(0, 217, 255, 0.8), 0 0 30px rgba(0, 217, 255, 0.4);
240
- font-family: 'Orbitron', monospace;
241
- letter-spacing: 2px;
242
- }
243
-
244
- .time-label {
245
- font-size: 13px;
246
- text-transform: uppercase;
247
- letter-spacing: 2px;
248
- margin: 8px 0 0 0;
249
- opacity: 0.9;
250
- font-weight: 700;
251
- }
252
-
253
- /* Expired State Styles */
254
- .expired-container {
255
- font-family: 'Bebas Neue', 'Anton', 'Oswald', sans-serif;
256
- background: linear-gradient(135deg, #2d1b00 0%, #5c3a1f 50%, #8b4513 100%);
257
- color: #ffa500;
258
- padding: 48px 24px;
259
- border-radius: 20px;
260
- text-align: center;
261
- box-shadow: 0 20px 60px rgba(255, 165, 0, 0.4), inset 0 0 40px rgba(255, 165, 0, 0.1);
262
- max-width: 600px;
263
- margin: 0 auto;
264
- border: 3px solid #ffa500;
265
- }
266
-
267
- .expired-icon {
268
- font-size: 80px;
269
- margin-bottom: 16px;
270
- animation: bounce 1s ease-in-out infinite;
271
- }
272
-
273
- .expired-title {
274
- font-size: 48px;
275
- font-weight: 900;
276
- margin: 0 0 16px 0;
277
- text-shadow: 0 0 20px rgba(255, 165, 0, 0.8), 0 4px 8px rgba(0, 0, 0, 0.5);
278
- letter-spacing: 4px;
279
- text-transform: uppercase;
280
- }
281
-
282
- .expired-message {
283
- font-size: 20px;
284
- margin: 0;
285
- opacity: 0.95;
286
- font-weight: 600;
287
- letter-spacing: 1px;
288
- }
289
-
290
- /* No Config State Styles */
291
- .no-config-container {
292
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
293
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
294
- color: #2c3e50;
295
- padding: 40px 24px;
296
- border-radius: 16px;
297
- text-align: center;
298
- border: 2px dashed #95a5a6;
299
- max-width: 500px;
300
- margin: 0 auto;
301
- }
302
-
303
- .no-config-icon {
304
- font-size: 64px;
305
- margin-bottom: 16px;
306
- }
307
-
308
- .no-config-title {
309
- font-size: 24px;
310
- font-weight: 700;
311
- margin: 0 0 12px 0;
312
- color: #34495e;
313
- }
314
-
315
- .no-config-message {
316
- font-size: 16px;
317
- margin: 0;
318
- line-height: 1.6;
319
- color: #7f8c8d;
320
- }
321
-
322
- /* Error State Styles */
323
- .error-container {
324
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
325
- background: #fff5f5;
326
- color: #c53030;
327
- padding: 32px 24px;
328
- border-radius: 12px;
329
- text-align: center;
330
- border: 2px solid #fc8181;
331
- max-width: 500px;
332
- margin: 0 auto;
333
- }
334
-
335
- .error-icon {
336
- font-size: 48px;
337
- margin-bottom: 12px;
338
- }
339
-
340
- .error-message {
341
- margin: 0;
342
- font-size: 16px;
343
- font-weight: 600;
344
- }
345
-
346
- /* Animations */
347
- @keyframes pulse {
348
- 0%, 100% {
349
- opacity: 1;
350
- transform: scale(1);
351
- }
352
- 50% {
353
- opacity: 0.6;
354
- transform: scale(1.1);
355
- }
356
- }
357
-
358
- @keyframes rotate {
359
- from {
360
- transform: rotate(0deg);
361
- }
362
- to {
363
- transform: rotate(360deg);
364
- }
365
- }
366
-
367
- @keyframes bounce {
368
- 0%, 100% {
369
- transform: translateY(0);
370
- }
371
- 50% {
372
- transform: translateY(-20px);
373
- }
374
- }
375
-
376
- /* Responsive Styles */
377
- @media (max-width: 600px) {
378
- .timer-grid {
379
- grid-template-columns: repeat(2, 1fr);
380
- gap: 16px;
381
- }
382
-
383
- .time-number {
384
- font-size: 36px;
385
- }
386
-
387
- .timer-container {
388
- padding: 24px 16px;
389
- }
390
-
391
- .timer-title {
392
- font-size: 24px;
393
- }
394
- }
395
- </style>
396
- `;
397
- }
398
-
399
- private renderEditorPlaceholder(): void {
400
- this.innerHTML = `
401
- ${this.getStyles()}
402
- <div class="editor-container" data-testid="countdown-timer-editor">
403
- <div class="editor-icon">⏱️</div>
404
- <h2 class="editor-title">COUNTDOWN TIMER</h2>
405
- <p class="editor-message">
406
- This widget will display a live countdown on your published site.<br>
407
- Configure the target date in your dashboard to get started.
408
- </p>
409
- </div>
410
- `;
411
- }
412
-
413
- private renderCountdown(timeRemaining: TimeRemaining): void {
414
- if (!this.config) {
415
- return;
416
- }
417
-
418
- const timeUnits = [
419
- { value: timeRemaining.days, label: 'Days' },
420
- { value: timeRemaining.hours, label: 'Hours' },
421
- { value: timeRemaining.minutes, label: 'Minutes' },
422
- { value: timeRemaining.seconds, label: 'Seconds' }
423
- ];
424
-
425
- const timeUnitsHtml = timeUnits
426
- .map(unit => `
427
- <div class="time-unit">
428
- <div class="time-number">${unit.value.toString().padStart(2, '0')}</div>
429
- <div class="time-label">${unit.label}</div>
430
- </div>
431
- `)
432
- .join('');
433
-
434
- const titleHtml = this.config.title
435
- ? `<h2 class="timer-title">${this.escapeHtml(this.config.title)}</h2>`
436
- : '';
437
-
438
- this.innerHTML = `
439
- ${this.getStyles()}
440
- <div class="timer-container" data-testid="countdown-timer">
441
- <div class="timer-glow"></div>
442
- ${titleHtml}
443
- <div class="timer-grid">
444
- ${timeUnitsHtml}
445
- </div>
446
- </div>
447
- `;
448
- }
449
-
450
- private renderExpired(): void {
451
- const message = this.config && this.config.title
452
- ? `${this.escapeHtml(this.config.title)} has ended.`
453
- : 'The countdown has ended.';
454
-
455
- this.innerHTML = `
456
- ${this.getStyles()}
457
- <div class="expired-container" data-testid="countdown-timer-expired">
458
- <div class="expired-icon">🎉</div>
459
- <h2 class="expired-title">Time's Up!</h2>
460
- <p class="expired-message">${message}</p>
461
- </div>
462
- `;
463
- }
464
-
465
- private renderNoConfig(): void {
466
- this.innerHTML = `
467
- ${this.getStyles()}
468
- <div class="no-config-container" data-testid="countdown-timer-no-config">
469
- <div class="no-config-icon">⚙️</div>
470
- <h2 class="no-config-title">No Active Countdown</h2>
471
- <p class="no-config-message">
472
- Please configure a countdown timer in your dashboard to display it here.
473
- </p>
474
- </div>
475
- `;
476
- }
477
-
478
- private renderError(): void {
479
- this.innerHTML = `
480
- ${this.getStyles()}
481
- <div class="error-container" data-testid="countdown-timer-error">
482
- <div class="error-icon">⚠️</div>
483
- <p class="error-message">Unable to load countdown timer</p>
484
- </div>
485
- `;
486
- }
487
-
488
- private escapeHtml(text: string): string {
489
- const div = document.createElement('div');
490
- div.textContent = text;
491
- return div.innerHTML;
492
- }
493
- }