socialcalc-ai 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +268 -0
  2. package/components/CellEditModal/CellEditModal.css +584 -0
  3. package/components/CellEditModal/CellEditModal.tsx +817 -0
  4. package/components/DemoVideosModal/DemoVideosModal.css +307 -0
  5. package/components/DemoVideosModal/DemoVideosModal.tsx +289 -0
  6. package/components/EditableCellsModal/EditableCellsModal.css +346 -0
  7. package/components/EditableCellsModal/EditableCellsModal.tsx +555 -0
  8. package/components/HorizontalScrollBar.css +127 -0
  9. package/components/HorizontalScrollBar.tsx +140 -0
  10. package/components/RowActionPopover/RowActionPopover.css +95 -0
  11. package/components/RowActionPopover/RowActionPopover.tsx +82 -0
  12. package/core/constants.js +1039 -0
  13. package/core/core.js +7172 -0
  14. package/core/format-number.js +7034 -0
  15. package/core/formula.js +1675 -0
  16. package/core/index.js +28 -0
  17. package/core/spreadsheet-control.js +7646 -0
  18. package/core/table-editor.js +8156 -0
  19. package/index.d.ts +68 -0
  20. package/index.js +43 -0
  21. package/modules/device.js +22 -0
  22. package/modules/editable-cells.js +230 -0
  23. package/modules/exporters.js +22 -0
  24. package/modules/formatting.js +258 -0
  25. package/modules/grid-lines.js +127 -0
  26. package/modules/history.js +26 -0
  27. package/modules/horizontal-scroll.js +335 -0
  28. package/modules/init.js +94 -0
  29. package/modules/invoice.js +558 -0
  30. package/modules/listeners.js +531 -0
  31. package/modules/logos.js +191 -0
  32. package/modules/plugin-manager.js +208 -0
  33. package/modules/prompts.js +170 -0
  34. package/modules/row-col-headers.js +514 -0
  35. package/modules/sheets.js +151 -0
  36. package/modules/touch-scroll.js +474 -0
  37. package/modules/utils.js +317 -0
  38. package/modules/weight.js +287 -0
  39. package/package.json +67 -0
  40. package/utils/imageCompressor.ts +118 -0
  41. package/utils/scriptLoader.ts +39 -0
@@ -0,0 +1,307 @@
1
+ /* Demo Videos Modal — Simple, Clean & Professional */
2
+ .demo-videos-modal {
3
+ --border-radius: 20px;
4
+ --background: #ffffff;
5
+ --width: 95vw;
6
+ --max-width: 800px;
7
+ --height: 720px;
8
+ --max-height: 92vh;
9
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
10
+ }
11
+
12
+ @media (min-width: 768px) {
13
+ .demo-videos-modal {
14
+ --width: 800px;
15
+ --height: 720px;
16
+ --max-height: 92vh;
17
+ }
18
+ }
19
+
20
+ /* Header */
21
+ .demo-videos-modal-toolbar {
22
+ --min-height: 48px;
23
+ --background: var(--ion-color-primary, #2563eb);
24
+ --color: #ffffff;
25
+ }
26
+
27
+ .demo-modal-title-wrapper {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 8px;
31
+ padding-left: 12px;
32
+ }
33
+
34
+ .demo-modal-header-icon {
35
+ font-size: 22px;
36
+ color: #ffffff;
37
+ flex-shrink: 0;
38
+ }
39
+
40
+ .demo-modal-title-text {
41
+ font-size: 17px;
42
+ font-weight: 700;
43
+ color: #ffffff;
44
+ letter-spacing: -0.01em;
45
+ white-space: nowrap;
46
+ }
47
+
48
+ .demo-modal-close-btn {
49
+ --color: #ffffff;
50
+ font-size: 20px;
51
+ }
52
+
53
+ /* Content */
54
+ .demo-videos-modal-content {
55
+ --background: #ffffff;
56
+ }
57
+
58
+ .demo-slider-wrapper {
59
+ max-width: 100%;
60
+ margin: 0 auto;
61
+ padding: 16px 24px 20px;
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 14px;
65
+ }
66
+
67
+ /* Video Title */
68
+ .demo-video-title {
69
+ margin: 0;
70
+ font-size: 19px;
71
+ font-weight: 700;
72
+ color: #0f172a;
73
+ text-align: center;
74
+ letter-spacing: -0.01em;
75
+ }
76
+
77
+ /* Video Card */
78
+ .demo-video-card {
79
+ position: relative;
80
+ background: #0f172a;
81
+ border-radius: 16px;
82
+ overflow: hidden;
83
+ box-shadow: 0 8px 24px -6px rgba(0, 0, 0, 0.16);
84
+ aspect-ratio: 16 / 10;
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: center;
88
+ cursor: pointer;
89
+ border: 1px solid #e2e8f0;
90
+ }
91
+
92
+ .demo-video-element {
93
+ width: 100%;
94
+ height: 100%;
95
+ object-fit: contain;
96
+ background: #000;
97
+ }
98
+
99
+ /* Play/Pause Overlay */
100
+ .demo-video-play-btn {
101
+ position: absolute;
102
+ top: 50%;
103
+ left: 50%;
104
+ transform: translate(-50%, -50%);
105
+ width: 56px;
106
+ height: 56px;
107
+ border-radius: 50%;
108
+ background: rgba(0, 0, 0, 0.55);
109
+ backdrop-filter: blur(6px);
110
+ border: 2px solid rgba(255, 255, 255, 0.7);
111
+ color: #ffffff;
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ font-size: 26px;
116
+ cursor: pointer;
117
+ opacity: 0;
118
+ transition: opacity 0.2s ease;
119
+ z-index: 5;
120
+ }
121
+
122
+ .demo-video-play-btn.visible,
123
+ .demo-video-card:hover .demo-video-play-btn {
124
+ opacity: 1;
125
+ }
126
+
127
+ .demo-video-play-btn:active {
128
+ transform: translate(-50%, -50%) scale(0.92);
129
+ }
130
+
131
+ /* Counter badge */
132
+ .demo-video-counter {
133
+ position: absolute;
134
+ top: 12px;
135
+ right: 12px;
136
+ background: rgba(0, 0, 0, 0.6);
137
+ backdrop-filter: blur(4px);
138
+ color: #ffffff;
139
+ font-size: 12px;
140
+ font-weight: 700;
141
+ padding: 4px 10px;
142
+ border-radius: 16px;
143
+ z-index: 4;
144
+ }
145
+
146
+ /* 1-sentence Description */
147
+ .demo-video-description {
148
+ margin: 0;
149
+ font-size: 15px;
150
+ line-height: 1.5;
151
+ color: #334155;
152
+ text-align: center;
153
+ padding: 0 12px;
154
+ }
155
+
156
+ /* Navigation Controls */
157
+ .demo-slider-nav {
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ gap: 20px;
162
+ padding-top: 6px;
163
+ }
164
+
165
+ .demo-nav-btn {
166
+ --color: #475569;
167
+ font-size: 26px;
168
+ margin: 0;
169
+ }
170
+
171
+ .demo-dots {
172
+ display: flex;
173
+ align-items: center;
174
+ gap: 8px;
175
+ }
176
+
177
+ .demo-dot {
178
+ width: 9px;
179
+ height: 9px;
180
+ border-radius: 5px;
181
+ background: #cbd5e1;
182
+ cursor: pointer;
183
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
184
+ }
185
+
186
+ .demo-dot.active {
187
+ width: 26px;
188
+ background: var(--ion-color-primary, #2563eb);
189
+ }
190
+
191
+ /* Pure White Theme Overrides */
192
+ [data-theme="pure-white"] .demo-dot {
193
+ background: #94a3b8;
194
+ }
195
+
196
+ [data-theme="pure-white"] .demo-dot.active {
197
+ background: #0f172a;
198
+ }
199
+
200
+ /* Formula Guide Section */
201
+ .formula-guide-section {
202
+ background: #f8fafc;
203
+ border: 1px solid #e2e8f0;
204
+ border-radius: 14px;
205
+ padding: 14px 16px;
206
+ margin-top: 4px;
207
+ text-align: left;
208
+ }
209
+
210
+ .formula-guide-header {
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: space-between;
214
+ flex-wrap: wrap;
215
+ gap: 8px;
216
+ margin-bottom: 12px;
217
+ padding-bottom: 8px;
218
+ border-bottom: 1px solid #e2e8f0;
219
+ }
220
+
221
+ .formula-guide-header-title {
222
+ display: flex;
223
+ align-items: center;
224
+ gap: 8px;
225
+ font-size: 14px;
226
+ font-weight: 700;
227
+ color: #1e293b;
228
+ }
229
+
230
+ .formula-guide-icon {
231
+ font-size: 18px;
232
+ color: var(--ion-color-primary, #2563eb);
233
+ }
234
+
235
+ .formula-guide-tip {
236
+ font-size: 11px;
237
+ color: #475569;
238
+ background: #eff6ff;
239
+ border: 1px solid #bfdbfe;
240
+ padding: 3px 8px;
241
+ border-radius: 6px;
242
+ display: inline-flex;
243
+ align-items: center;
244
+ gap: 4px;
245
+ font-weight: 500;
246
+ }
247
+
248
+ .formula-guide-tip code {
249
+ font-weight: 700;
250
+ color: #1d4ed8;
251
+ }
252
+
253
+ .formula-guide-grid {
254
+ display: grid;
255
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
256
+ gap: 10px;
257
+ }
258
+
259
+ .formula-guide-card {
260
+ background: #ffffff;
261
+ border: 1px solid #e2e8f0;
262
+ border-radius: 10px;
263
+ padding: 10px 12px;
264
+ display: flex;
265
+ flex-direction: column;
266
+ gap: 4px;
267
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
268
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
269
+ }
270
+
271
+ .formula-guide-card:hover {
272
+ border-color: #cbd5e1;
273
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.06);
274
+ }
275
+
276
+ .formula-card-top {
277
+ display: flex;
278
+ align-items: center;
279
+ justify-content: space-between;
280
+ gap: 8px;
281
+ }
282
+
283
+ .formula-name-badge {
284
+ font-size: 10px;
285
+ font-weight: 700;
286
+ background: #eff6ff;
287
+ color: #1d4ed8;
288
+ padding: 2px 6px;
289
+ border-radius: 4px;
290
+ letter-spacing: 0.03em;
291
+ }
292
+
293
+ .formula-code {
294
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
295
+ font-size: 12px;
296
+ font-weight: 700;
297
+ color: #0f172a;
298
+ background: #f1f5f9;
299
+ padding: 2px 6px;
300
+ border-radius: 4px;
301
+ }
302
+
303
+ .formula-card-desc {
304
+ font-size: 11px;
305
+ color: #64748b;
306
+ line-height: 1.35;
307
+ }
@@ -0,0 +1,289 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import {
3
+ IonModal,
4
+ IonHeader,
5
+ IonToolbar,
6
+ IonTitle,
7
+ IonButtons,
8
+ IonButton,
9
+ IonIcon,
10
+ IonContent,
11
+ } from "@ionic/react";
12
+ import {
13
+ closeOutline,
14
+ chevronBackOutline,
15
+ chevronForwardOutline,
16
+ playOutline,
17
+ pauseOutline,
18
+ videocamOutline,
19
+ calculatorOutline,
20
+ bulbOutline,
21
+ helpCircleOutline,
22
+ } from "ionicons/icons";
23
+ import "./DemoVideosModal.css";
24
+
25
+ export interface DemoVideoItem {
26
+ id: string;
27
+ title: string;
28
+ description: string;
29
+ videoSrc: string;
30
+ }
31
+
32
+ export interface FormulaGuideItem {
33
+ name: string;
34
+ example: string;
35
+ desc: string;
36
+ }
37
+
38
+ export const FORMULA_GUIDES: FormulaGuideItem[] = [
39
+ {
40
+ name: "TODAY (CURRENT DATE)",
41
+ example: "=TODAY()",
42
+ desc: "Automatically shows today's live date (ideal for Invoice Date)",
43
+ },
44
+ {
45
+ name: "SUM",
46
+ example: "=SUM(F14:F26)",
47
+ desc: "Adds all numbers in a range (e.g. Subtotal of line items)",
48
+ },
49
+ {
50
+ name: "AVERAGE",
51
+ example: "=AVERAGE(D14:D26)",
52
+ desc: "Calculates the average value of selected cells",
53
+ },
54
+ {
55
+ name: "MIN & MAX",
56
+ example: "=MIN(F14:F26) , =MAX(F14:F26)",
57
+ desc: "Finds the lowest or highest value in the column",
58
+ },
59
+ {
60
+ name: "PERCENTAGE (%)",
61
+ example: "=F34 * 18%",
62
+ desc: "Calculates tax (GST / VAT) or percentage discount",
63
+ },
64
+ {
65
+ name: "MULTIPLY (*)",
66
+ example: "=C14 * D14",
67
+ desc: "Multiplies Quantity by Unit Price for line items",
68
+ },
69
+ {
70
+ name: "SUBTRACT & ADD",
71
+ example: "=(F34 - G35) + G36",
72
+ desc: "(Subtotal - Discount) + Shipping / Extra Charges",
73
+ },
74
+ {
75
+ name: "IF CONDITION",
76
+ example: '=IF(C14>0, C14*D14, "")',
77
+ desc: "Only calculates when Quantity is entered (keeps empty rows clean)",
78
+ },
79
+ {
80
+ name: "ROUND",
81
+ example: "=ROUND(F34, 2)",
82
+ desc: "Rounds invoice total to 2 decimal places",
83
+ },
84
+ ];
85
+
86
+ export const DEMO_VIDEOS: DemoVideoItem[] = [
87
+ {
88
+ id: "col-resizing",
89
+ title: "Column Resizing",
90
+ description: "Drag column borders or use the corner touch handle to adjust column width.",
91
+ videoSrc: "/videos/col-resizing.mp4",
92
+ },
93
+ {
94
+ id: "row-demo",
95
+ title: "Row Actions",
96
+ description: "Tap any row number to open quick actions for inserting or deleting rows.",
97
+ videoSrc: "/videos/row-demo.mp4",
98
+ },
99
+ {
100
+ id: "custom-formulas",
101
+ title: "Custom Formulas",
102
+ description: "Write and calculate custom formulas (e.g. =TODAY(), SUM, IF, multiplication) directly in your invoice cells.",
103
+ videoSrc: "/videos/custom-formulas.mp4",
104
+ },
105
+ {
106
+ id: "cell-modal",
107
+ title: "Cell Editing",
108
+ description: "Tap any cell to update values, formulas, and customize font/background colors.",
109
+ videoSrc: "/videos/cell-model-input-model.mp4",
110
+ },
111
+ ];
112
+
113
+ export interface DemoVideosModalProps {
114
+ isOpen: boolean;
115
+ onClose: () => void;
116
+ videos?: DemoVideoItem[];
117
+ }
118
+
119
+ export const DemoVideosModal: React.FC<DemoVideosModalProps> = ({ isOpen, onClose, videos = DEMO_VIDEOS }) => {
120
+ const [currentIndex, setCurrentIndex] = useState<number>(0);
121
+ const [isPlaying, setIsPlaying] = useState<boolean>(true);
122
+ const videoRef = useRef<HTMLVideoElement | null>(null);
123
+
124
+ const videoList = videos && videos.length > 0 ? videos : DEMO_VIDEOS;
125
+ const currentVideo = videoList[currentIndex] || videoList[0];
126
+
127
+ useEffect(() => {
128
+ if (isOpen) {
129
+ setIsPlaying(true);
130
+ if (videoRef.current) {
131
+ videoRef.current.currentTime = 0;
132
+ videoRef.current.play().catch(() => {
133
+ setIsPlaying(false);
134
+ });
135
+ }
136
+ } else {
137
+ if (videoRef.current) {
138
+ videoRef.current.pause();
139
+ }
140
+ }
141
+ }, [isOpen, currentIndex]);
142
+
143
+ const handleNext = () => {
144
+ setCurrentIndex((prev) => (prev + 1) % videoList.length);
145
+ };
146
+
147
+ const handlePrev = () => {
148
+ setCurrentIndex((prev) => (prev - 1 + videoList.length) % videoList.length);
149
+ };
150
+
151
+ const togglePlayPause = () => {
152
+ if (videoRef.current) {
153
+ if (videoRef.current.paused) {
154
+ videoRef.current.play().then(() => setIsPlaying(true)).catch(() => {});
155
+ } else {
156
+ videoRef.current.pause();
157
+ setIsPlaying(false);
158
+ }
159
+ }
160
+ };
161
+
162
+ return (
163
+ <IonModal
164
+ isOpen={isOpen}
165
+ onDidDismiss={onClose}
166
+ className="demo-videos-modal"
167
+ >
168
+ <IonHeader className="ion-no-border">
169
+ <IonToolbar color="primary" className="demo-videos-modal-toolbar">
170
+ <IonButtons slot="start">
171
+ <div className="demo-modal-title-wrapper">
172
+ <IonIcon icon={helpCircleOutline} className="demo-modal-header-icon" />
173
+ <span className="demo-modal-title-text">Help</span>
174
+ </div>
175
+ </IonButtons>
176
+ <IonButtons slot="end">
177
+ <IonButton fill="clear" onClick={onClose} className="demo-modal-close-btn">
178
+ <IonIcon icon={closeOutline} slot="icon-only" />
179
+ </IonButton>
180
+ </IonButtons>
181
+ </IonToolbar>
182
+ </IonHeader>
183
+
184
+ <IonContent className="demo-videos-modal-content">
185
+ <div className="demo-slider-wrapper">
186
+ {/* Video Title */}
187
+ <h3 className="demo-video-title">{currentVideo.title}</h3>
188
+
189
+ {/* Video Player */}
190
+ <div className="demo-video-card" onClick={togglePlayPause}>
191
+ <video
192
+ ref={videoRef}
193
+ src={currentVideo.videoSrc}
194
+ playsInline
195
+ muted
196
+ loop
197
+ autoPlay
198
+ className="demo-video-element"
199
+ onPlay={() => setIsPlaying(true)}
200
+ onPause={() => setIsPlaying(false)}
201
+ />
202
+
203
+ {/* Play/Pause Overlay */}
204
+ <button
205
+ type="button"
206
+ className={`demo-video-play-btn ${!isPlaying ? "visible" : ""}`}
207
+ aria-label={isPlaying ? "Pause video" : "Play video"}
208
+ onClick={(e) => {
209
+ e.stopPropagation();
210
+ togglePlayPause();
211
+ }}
212
+ >
213
+ <IonIcon icon={isPlaying ? pauseOutline : playOutline} />
214
+ </button>
215
+
216
+ {/* Counter */}
217
+ <div className="demo-video-counter">
218
+ {currentIndex + 1} / {videoList.length}
219
+ </div>
220
+ </div>
221
+
222
+ {/* One-sentence Description */}
223
+ <p className="demo-video-description">{currentVideo.description}</p>
224
+
225
+ {/* Basic Formulas Guide (Shown on Custom Formulas Slide) */}
226
+ {currentVideo.id === "custom-formulas" && (
227
+ <div className="formula-guide-section">
228
+ <div className="formula-guide-header">
229
+ <div className="formula-guide-header-title">
230
+ <IonIcon icon={calculatorOutline} className="formula-guide-icon" />
231
+ <span>Basic Formulas Quick Reference</span>
232
+ </div>
233
+ <span className="formula-guide-tip">
234
+ <IonIcon icon={bulbOutline} />
235
+ Start formulas with <code>=</code>
236
+ </span>
237
+ </div>
238
+
239
+ <div className="formula-guide-grid">
240
+ {FORMULA_GUIDES.map((f, i) => (
241
+ <div key={i} className="formula-guide-card">
242
+ <div className="formula-card-top">
243
+ <span className="formula-name-badge">{f.name}</span>
244
+ <code className="formula-code">{f.example}</code>
245
+ </div>
246
+ <div className="formula-card-desc">{f.desc}</div>
247
+ </div>
248
+ ))}
249
+ </div>
250
+ </div>
251
+ )}
252
+
253
+ {/* Navigation Controls */}
254
+ <div className="demo-slider-nav">
255
+ <IonButton
256
+ fill="clear"
257
+ size="small"
258
+ onClick={handlePrev}
259
+ className="demo-nav-btn"
260
+ >
261
+ <IonIcon icon={chevronBackOutline} slot="icon-only" />
262
+ </IonButton>
263
+
264
+ <div className="demo-dots">
265
+ {videoList.map((_, idx) => (
266
+ <span
267
+ key={idx}
268
+ className={`demo-dot ${idx === currentIndex ? "active" : ""}`}
269
+ onClick={() => setCurrentIndex(idx)}
270
+ />
271
+ ))}
272
+ </div>
273
+
274
+ <IonButton
275
+ fill="clear"
276
+ size="small"
277
+ onClick={handleNext}
278
+ className="demo-nav-btn"
279
+ >
280
+ <IonIcon icon={chevronForwardOutline} slot="icon-only" />
281
+ </IonButton>
282
+ </div>
283
+ </div>
284
+ </IonContent>
285
+ </IonModal>
286
+ );
287
+ };
288
+
289
+ export default DemoVideosModal;