@wix/ditto-codegen-public 1.0.181 → 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
@@ -37455,7 +37455,23 @@ When the blueprint includes EMBEDDED_SCRIPT extensions:
37455
37455
  * User-generated content (reviews, comments, submissions)
37456
37456
  * Event logs (if explicitly requested for analytics/tracking purposes)
37457
37457
  * Multi-record relational data that is NOT configuration
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`;
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`;
37459
37475
  var cmsPlannerExample = `Blueprint indicates a handling fees system for products.
37460
37476
 
37461
37477
  For the collections field, return:
@@ -37476,6 +37492,12 @@ For the collections field, return:
37476
37492
  - Example (WRONG):
37477
37493
  * Creating both embeddedScriptParameters AND a "popup-configurations" CMS collection - this is duplicate storage
37478
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
37479
37501
  5) Initial Data Assessment:
37480
37502
  - After defining the collection schema, evaluate if initial/seed data can be implied from the blueprint
37481
37503
  - Consider if the blueprint describes example data, default configurations, sample content, or starter records that would help demonstrate the app's functionality
@@ -64455,6 +64477,7 @@ var require_dashboard_page_instructions = __commonJS({
64455
64477
  "Page.Section",
64456
64478
  "RichTextInputArea",
64457
64479
  "SectionHeader",
64480
+ "SidePanel",
64458
64481
  "Table",
64459
64482
  "TableActionCell",
64460
64483
  "TableListHeader",
@@ -64604,7 +64627,14 @@ var require_load_examples = __commonJS({
64604
64627
  description: "A Custom Element that displays a countdown timer",
64605
64628
  files: {
64606
64629
  [types_1.ExtensionType.SITE_WIDGET]: [
64607
- "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"
64608
64638
  ]
64609
64639
  }
64610
64640
  },
@@ -65797,18 +65827,39 @@ var require_custom_element_instructions = __commonJS({
65797
65827
  Object.defineProperty(exports2, "__esModule", { value: true });
65798
65828
  exports2.customElementInstructions = void 0;
65799
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();
65800
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.`;
65801
65833
  var customElementImplementationGuidelines = `<rules>
65802
65834
  - Return ONLY a JSON object matching the schema { path, content, type } (no prose, no markdown)
65803
65835
  - type must be "typescript"; content must be the COMPLETE file source
65804
- - Do NOT add dependencies; do NOT use @wix/design-system or @wix/wix-ui-icons-common
65805
65836
  - Do NOT invent types/modules/props; use only what exists in the scaffold and standard libs
65806
65837
  </rules>
65807
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
+
65808
65860
  <engineering_guidelines>
65809
65861
  - Implement a TypeScript custom element with proper typing and clear interfaces
65810
- - Keep the component small, pure, and readable; remove dead code/unused imports
65811
- - 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)
65812
65863
  </engineering_guidelines>
65813
65864
 
65814
65865
  ${typescript_quality_guidelines_1.typescriptQualityGuidelines}
@@ -65843,7 +65894,8 @@ var require_custom_element_instructions = __commonJS({
65843
65894
  exports2.customElementInstructions = {
65844
65895
  role: customElementRole,
65845
65896
  implementationGuidelines: customElementImplementationGuidelines,
65846
- implementationRequirements: customElementImplementationRequirements
65897
+ implementationRequirements: customElementImplementationRequirements,
65898
+ wdsReference: () => (0, wdsPackage_1.buildWdsSystemPrompt)(dashboard_page_instructions_1.supportedWDSComponents)
65847
65899
  };
65848
65900
  }
65849
65901
  });
@@ -65863,7 +65915,7 @@ var require_custom_element_prompt = __commonJS({
65863
65915
  apiNames,
65864
65916
  useData,
65865
65917
  useIteration
65866
- }).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();
65867
65919
  if (useData) {
65868
65920
  systemPrompt.withSection("implementation_requirements", custom_element_instructions_1.customElementInstructions.implementationRequirements);
65869
65921
  }
@@ -78563,11 +78615,13 @@ var require_extensionGenerators = __commonJS({
78563
78615
  static createCustomElementData(id, name, scaffoldDir) {
78564
78616
  const kebabCaseComponentName = (0, ditto_scaffolding_2.toKebabCase)(name);
78565
78617
  const componentPath = getScaffoldPath(scaffoldDir, "widget.tsx");
78618
+ const settingsPath = getScaffoldPath(scaffoldDir, "panel.tsx");
78566
78619
  return {
78567
78620
  id,
78568
78621
  name,
78569
78622
  tagName: kebabCaseComponentName,
78570
78623
  element: componentPath,
78624
+ settings: settingsPath,
78571
78625
  installation: { autoAdd: true },
78572
78626
  width: { defaultWidth: 500, allowStretch: true },
78573
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.181",
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": "151737a032f3a8f5f4f0c4f06b5f3327f6ae413df19d0c5acb41af5d"
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
- }