frontend-hamroun 1.2.82 → 1.2.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +58 -870
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.client.cjs +1 -1
- package/dist/index.client.cjs.map +1 -1
- package/dist/index.client.js +2 -2
- package/dist/index.client.js.map +1 -1
- package/dist/index.js +116 -133
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.cjs.map +1 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/renderer-DaVfBeVi.cjs +2 -0
- package/dist/renderer-DaVfBeVi.cjs.map +1 -0
- package/dist/renderer-nfT7XSpo.js +61 -0
- package/dist/renderer-nfT7XSpo.js.map +1 -0
- package/dist/server-renderer-B5b0Q0ck.cjs +2 -0
- package/dist/server-renderer-B5b0Q0ck.cjs.map +1 -0
- package/dist/{server-renderer-QHt45Ip2.js → server-renderer-C4MB-jAp.js} +89 -96
- package/dist/server-renderer-C4MB-jAp.js.map +1 -0
- package/dist/server-renderer.cjs +1 -1
- package/dist/server-renderer.js +1 -1
- package/package.json +1 -1
- package/templates/basic-app/src/App.tsx +397 -19
- package/templates/ssr-template/dist/client/assets/main-D-VH3xOb.js +1 -0
- package/templates/ssr-template/dist/client/index.html +23 -0
- package/templates/ssr-template/dist/client.js +951 -0
- package/templates/ssr-template/dist/server.js +739 -0
- package/templates/ssr-template/package.json +22 -22
- package/templates/ssr-template/src/App.tsx +874 -11
- package/templates/ssr-template/tsconfig.json +14 -10
- package/templates/ssr-template/vite.config.ts +19 -17
- package/templates/wasm/dist/assets/index-BNqTDBdE.js +295 -0
- package/templates/wasm/dist/example.wasm +0 -0
- package/templates/wasm/dist/index.html +53 -0
- package/templates/wasm/dist/wasm_exec.js +572 -0
- package/templates/wasm/package-lock.json +4577 -5307
- package/templates/wasm/package.json +25 -42
- package/templates/wasm/public/wasm_exec.js +12 -1
- package/templates/wasm/src/App.tsx +41 -55
- package/templates/wasm/vite.config.ts +24 -42
- package/dist/renderer-Bo9zkUZ_.js +0 -52
- package/dist/renderer-Bo9zkUZ_.js.map +0 -1
- package/dist/renderer-Din1y3YM.cjs +0 -2
- package/dist/renderer-Din1y3YM.cjs.map +0 -1
- package/dist/server-renderer-CqIpQ-od.cjs +0 -2
- package/dist/server-renderer-CqIpQ-od.cjs.map +0 -1
- package/dist/server-renderer-QHt45Ip2.js.map +0 -1
@@ -0,0 +1,951 @@
|
|
1
|
+
// node_modules/frontend-hamroun/dist/index.mjs
|
2
|
+
var isBatching = false;
|
3
|
+
var queue = [];
|
4
|
+
function batchUpdates(fn) {
|
5
|
+
if (isBatching) {
|
6
|
+
queue.push(fn);
|
7
|
+
return;
|
8
|
+
}
|
9
|
+
isBatching = true;
|
10
|
+
try {
|
11
|
+
fn();
|
12
|
+
while (queue.length > 0) {
|
13
|
+
const nextFn = queue.shift();
|
14
|
+
nextFn == null ? void 0 : nextFn();
|
15
|
+
}
|
16
|
+
} finally {
|
17
|
+
isBatching = false;
|
18
|
+
}
|
19
|
+
}
|
20
|
+
var currentRender = 0;
|
21
|
+
var states = /* @__PURE__ */ new Map();
|
22
|
+
var stateIndices = /* @__PURE__ */ new Map();
|
23
|
+
var effects = /* @__PURE__ */ new Map();
|
24
|
+
var memos = /* @__PURE__ */ new Map();
|
25
|
+
var globalRenderCallback = null;
|
26
|
+
var globalContainer = null;
|
27
|
+
var currentElement = null;
|
28
|
+
var isServer = typeof window === "undefined";
|
29
|
+
var serverStates = /* @__PURE__ */ new Map();
|
30
|
+
function setRenderCallback(callback, element, container) {
|
31
|
+
globalRenderCallback = callback;
|
32
|
+
globalContainer = container;
|
33
|
+
currentElement = element;
|
34
|
+
}
|
35
|
+
function prepareRender() {
|
36
|
+
currentRender++;
|
37
|
+
stateIndices.set(currentRender, 0);
|
38
|
+
return currentRender;
|
39
|
+
}
|
40
|
+
function finishRender() {
|
41
|
+
if (isServer) {
|
42
|
+
serverStates.delete(currentRender);
|
43
|
+
}
|
44
|
+
currentRender = 0;
|
45
|
+
}
|
46
|
+
function useState(initial) {
|
47
|
+
if (!currentRender) {
|
48
|
+
throw new Error("useState must be called within a render");
|
49
|
+
}
|
50
|
+
if (isServer) {
|
51
|
+
if (!serverStates.has(currentRender)) {
|
52
|
+
serverStates.set(currentRender, /* @__PURE__ */ new Map());
|
53
|
+
}
|
54
|
+
const componentState = serverStates.get(currentRender);
|
55
|
+
const index2 = stateIndices.get(currentRender) || 0;
|
56
|
+
if (!componentState.has(index2)) {
|
57
|
+
componentState.set(index2, initial);
|
58
|
+
}
|
59
|
+
const state2 = componentState.get(index2);
|
60
|
+
const setState2 = (newValue) => {
|
61
|
+
};
|
62
|
+
stateIndices.set(currentRender, index2 + 1);
|
63
|
+
return [state2, setState2];
|
64
|
+
}
|
65
|
+
if (!states.has(currentRender)) {
|
66
|
+
states.set(currentRender, []);
|
67
|
+
}
|
68
|
+
const componentStates = states.get(currentRender);
|
69
|
+
const index = stateIndices.get(currentRender);
|
70
|
+
if (index >= componentStates.length) {
|
71
|
+
componentStates.push(initial);
|
72
|
+
}
|
73
|
+
const state = componentStates[index];
|
74
|
+
const setState = (newValue) => {
|
75
|
+
const nextValue = typeof newValue === "function" ? newValue(componentStates[index]) : newValue;
|
76
|
+
if (componentStates[index] === nextValue)
|
77
|
+
return;
|
78
|
+
componentStates[index] = nextValue;
|
79
|
+
if (isBatching) {
|
80
|
+
batchUpdates(() => rerender(currentRender));
|
81
|
+
} else {
|
82
|
+
rerender(currentRender);
|
83
|
+
}
|
84
|
+
};
|
85
|
+
stateIndices.set(currentRender, index + 1);
|
86
|
+
return [state, setState];
|
87
|
+
}
|
88
|
+
function useEffect(callback, deps) {
|
89
|
+
if (!currentRender)
|
90
|
+
throw new Error("useEffect must be called within a render");
|
91
|
+
const effectIndex = stateIndices.get(currentRender);
|
92
|
+
if (!effects.has(currentRender)) {
|
93
|
+
effects.set(currentRender, []);
|
94
|
+
}
|
95
|
+
const componentEffects = effects.get(currentRender);
|
96
|
+
const prevEffect = componentEffects[effectIndex];
|
97
|
+
if (!prevEffect || !deps || !prevEffect.deps || deps.some((dep, i) => dep !== prevEffect.deps[i])) {
|
98
|
+
if (prevEffect == null ? void 0 : prevEffect.cleanup) {
|
99
|
+
prevEffect.cleanup();
|
100
|
+
}
|
101
|
+
queueMicrotask(() => {
|
102
|
+
const cleanup = callback() || void 0;
|
103
|
+
componentEffects[effectIndex] = { cleanup, deps };
|
104
|
+
});
|
105
|
+
}
|
106
|
+
stateIndices.set(currentRender, effectIndex + 1);
|
107
|
+
}
|
108
|
+
function useMemo(factory, deps) {
|
109
|
+
if (!currentRender)
|
110
|
+
throw new Error("useMemo must be called within a render");
|
111
|
+
const memoIndex = stateIndices.get(currentRender);
|
112
|
+
if (!memos.has(currentRender)) {
|
113
|
+
memos.set(currentRender, []);
|
114
|
+
}
|
115
|
+
const componentMemos = memos.get(currentRender);
|
116
|
+
const prevMemo = componentMemos[memoIndex];
|
117
|
+
if (!prevMemo || deps && deps.some((dep, i) => !Object.is(dep, prevMemo.deps[i]))) {
|
118
|
+
const value = factory();
|
119
|
+
componentMemos[memoIndex] = { value, deps };
|
120
|
+
stateIndices.set(currentRender, memoIndex + 1);
|
121
|
+
return value;
|
122
|
+
}
|
123
|
+
stateIndices.set(currentRender, memoIndex + 1);
|
124
|
+
return prevMemo.value;
|
125
|
+
}
|
126
|
+
async function rerender(rendererId) {
|
127
|
+
try {
|
128
|
+
const componentEffects = effects.get(rendererId);
|
129
|
+
if (componentEffects) {
|
130
|
+
componentEffects.forEach((effect) => {
|
131
|
+
if (effect.cleanup)
|
132
|
+
effect.cleanup();
|
133
|
+
});
|
134
|
+
effects.set(rendererId, []);
|
135
|
+
}
|
136
|
+
if (globalRenderCallback && globalContainer && currentElement) {
|
137
|
+
await globalRenderCallback(currentElement, globalContainer);
|
138
|
+
}
|
139
|
+
} catch (error) {
|
140
|
+
console.error("Error during rerender:", error);
|
141
|
+
}
|
142
|
+
}
|
143
|
+
function useErrorBoundary() {
|
144
|
+
const [error, setError] = useState(null);
|
145
|
+
return [error, () => setError(null)];
|
146
|
+
}
|
147
|
+
function jsx(type, props) {
|
148
|
+
console.log("JSX Transform:", { type, props });
|
149
|
+
const processedProps = { ...props };
|
150
|
+
if (arguments.length > 2) {
|
151
|
+
processedProps.children = Array.prototype.slice.call(arguments, 2);
|
152
|
+
}
|
153
|
+
return { type, props: processedProps };
|
154
|
+
}
|
155
|
+
async function createElement(vnode) {
|
156
|
+
var _a;
|
157
|
+
console.log("Creating element from:", vnode);
|
158
|
+
if (vnode == null) {
|
159
|
+
return document.createTextNode("");
|
160
|
+
}
|
161
|
+
if (typeof vnode === "boolean") {
|
162
|
+
return document.createTextNode("");
|
163
|
+
}
|
164
|
+
if (typeof vnode === "number" || typeof vnode === "string") {
|
165
|
+
return document.createTextNode(String(vnode));
|
166
|
+
}
|
167
|
+
if (Array.isArray(vnode)) {
|
168
|
+
const fragment = document.createDocumentFragment();
|
169
|
+
for (const child of vnode) {
|
170
|
+
const node = await createElement(child);
|
171
|
+
fragment.appendChild(node);
|
172
|
+
}
|
173
|
+
return fragment;
|
174
|
+
}
|
175
|
+
if ("type" in vnode && vnode.props !== void 0) {
|
176
|
+
const { type, props } = vnode;
|
177
|
+
if (typeof type === "function") {
|
178
|
+
try {
|
179
|
+
const result = await type(props || {});
|
180
|
+
const node = await createElement(result);
|
181
|
+
if (node instanceof Element) {
|
182
|
+
node.setAttribute("data-component-id", type.name || type.toString());
|
183
|
+
}
|
184
|
+
return node;
|
185
|
+
} catch (error) {
|
186
|
+
console.error("Error rendering component:", error);
|
187
|
+
return document.createTextNode("");
|
188
|
+
}
|
189
|
+
}
|
190
|
+
const element = document.createElement(type);
|
191
|
+
for (const [key, value] of Object.entries(props || {})) {
|
192
|
+
if (key === "children")
|
193
|
+
continue;
|
194
|
+
if (key.startsWith("on") && typeof value === "function") {
|
195
|
+
const eventName = key.toLowerCase().slice(2);
|
196
|
+
const existingHandler = (_a = element.__events) == null ? void 0 : _a[eventName];
|
197
|
+
if (existingHandler) {
|
198
|
+
element.removeEventListener(eventName, existingHandler);
|
199
|
+
}
|
200
|
+
element.addEventListener(eventName, value);
|
201
|
+
if (!element.__events) {
|
202
|
+
element.__events = {};
|
203
|
+
}
|
204
|
+
element.__events[eventName] = value;
|
205
|
+
} else if (key === "style" && typeof value === "object") {
|
206
|
+
Object.assign(element.style, value);
|
207
|
+
} else if (key === "className") {
|
208
|
+
element.setAttribute("class", String(value));
|
209
|
+
} else if (key !== "key" && key !== "ref") {
|
210
|
+
element.setAttribute(key, String(value));
|
211
|
+
}
|
212
|
+
}
|
213
|
+
const children = props == null ? void 0 : props.children;
|
214
|
+
if (children != null) {
|
215
|
+
const childArray = Array.isArray(children) ? children.flat() : [children];
|
216
|
+
for (const child of childArray) {
|
217
|
+
const childNode = await createElement(child);
|
218
|
+
element.appendChild(childNode);
|
219
|
+
}
|
220
|
+
}
|
221
|
+
return element;
|
222
|
+
}
|
223
|
+
return document.createTextNode(String(vnode));
|
224
|
+
}
|
225
|
+
var isHydrating = false;
|
226
|
+
async function hydrate(element, container) {
|
227
|
+
isHydrating = true;
|
228
|
+
try {
|
229
|
+
await render(element, container);
|
230
|
+
} finally {
|
231
|
+
isHydrating = false;
|
232
|
+
}
|
233
|
+
}
|
234
|
+
async function render(element, container) {
|
235
|
+
console.log("Rendering to:", container.id);
|
236
|
+
batchUpdates(async () => {
|
237
|
+
const rendererId = prepareRender();
|
238
|
+
try {
|
239
|
+
setRenderCallback(render, element, container);
|
240
|
+
const domNode = await createElement(element);
|
241
|
+
if (!isHydrating) {
|
242
|
+
container.innerHTML = "";
|
243
|
+
}
|
244
|
+
container.appendChild(domNode);
|
245
|
+
} finally {
|
246
|
+
finishRender();
|
247
|
+
}
|
248
|
+
});
|
249
|
+
}
|
250
|
+
|
251
|
+
// src/App.tsx
|
252
|
+
function TodoItem({ todo, onToggle, onDelete }) {
|
253
|
+
return /* @__PURE__ */ jsx("div", { className: `todo-item ${todo.completed ? "completed" : ""}` }, /* @__PURE__ */ jsx(
|
254
|
+
"input",
|
255
|
+
{
|
256
|
+
type: "checkbox",
|
257
|
+
checked: todo.completed,
|
258
|
+
onChange: () => onToggle(todo.id),
|
259
|
+
className: "todo-checkbox"
|
260
|
+
}
|
261
|
+
), /* @__PURE__ */ jsx("span", { className: "todo-text" }, todo.text), /* @__PURE__ */ jsx("span", { className: `todo-priority priority-${todo.priority}` }, todo.priority === "high" && "\u{1F534}", todo.priority === "medium" && "\u{1F7E1}", todo.priority === "low" && "\u{1F7E2}", todo.priority), /* @__PURE__ */ jsx("div", { className: "todo-actions" }, /* @__PURE__ */ jsx(
|
262
|
+
"button",
|
263
|
+
{
|
264
|
+
onClick: () => onDelete(todo.id),
|
265
|
+
className: "btn btn-sm btn-danger",
|
266
|
+
title: "Delete todo"
|
267
|
+
},
|
268
|
+
"\u{1F5D1}\uFE0F"
|
269
|
+
)));
|
270
|
+
}
|
271
|
+
function ThemeToggleButton({ theme, onToggle }) {
|
272
|
+
return /* @__PURE__ */ jsx(
|
273
|
+
"button",
|
274
|
+
{
|
275
|
+
onClick: onToggle,
|
276
|
+
className: "btn btn-secondary theme-toggle",
|
277
|
+
title: "Toggle theme"
|
278
|
+
},
|
279
|
+
theme === "light" ? "\u{1F319}" : "\u2600\uFE0F"
|
280
|
+
);
|
281
|
+
}
|
282
|
+
function AppFooter({ theme, isBrowser }) {
|
283
|
+
return /* @__PURE__ */ jsx("footer", { className: "app-footer" }, /* @__PURE__ */ jsx("div", { className: "container" }, /* @__PURE__ */ jsx("p", null, "Built with Frontend Hamroun \u2022 Hooks: useState, useEffect, useMemo, useErrorBoundary"), /* @__PURE__ */ jsx("p", null, "Theme: ", /* @__PURE__ */ jsx("strong", null, theme), " \u2022 Environment: ", /* @__PURE__ */ jsx("strong", null, isBrowser ? "Client" : "Server"))));
|
284
|
+
}
|
285
|
+
function App() {
|
286
|
+
const [todos, setTodos] = useState([
|
287
|
+
{ id: 1, text: "Learn Frontend Hamroun hooks", completed: false, priority: "high" },
|
288
|
+
{ id: 2, text: "Build a todo app", completed: false, priority: "medium" },
|
289
|
+
{ id: 3, text: "Master SSR concepts", completed: true, priority: "low" }
|
290
|
+
]);
|
291
|
+
const [newTask, setNewTask] = useState("");
|
292
|
+
const [taskFilter, setTaskFilter] = useState("all");
|
293
|
+
const [taskPriority, setTaskPriority] = useState("medium");
|
294
|
+
const [theme, setTheme] = useState("light");
|
295
|
+
const [isLoading, setIsLoading] = useState(false);
|
296
|
+
const [error, resetError] = useErrorBoundary();
|
297
|
+
const isBrowser = typeof window !== "undefined";
|
298
|
+
useEffect(() => {
|
299
|
+
if (!isBrowser)
|
300
|
+
return;
|
301
|
+
console.log("Todo App mounted");
|
302
|
+
const savedTodos = localStorage.getItem("todos");
|
303
|
+
const savedTheme = localStorage.getItem("theme");
|
304
|
+
if (savedTodos) {
|
305
|
+
try {
|
306
|
+
setTodos(JSON.parse(savedTodos));
|
307
|
+
} catch (e) {
|
308
|
+
console.error("Failed to load saved todos");
|
309
|
+
}
|
310
|
+
}
|
311
|
+
if (savedTheme) {
|
312
|
+
setTheme(savedTheme);
|
313
|
+
}
|
314
|
+
return () => {
|
315
|
+
console.log("Todo App unmounting");
|
316
|
+
};
|
317
|
+
}, []);
|
318
|
+
useEffect(() => {
|
319
|
+
if (!isBrowser)
|
320
|
+
return;
|
321
|
+
document.body.className = `theme-${theme}`;
|
322
|
+
document.documentElement.setAttribute("data-theme", theme);
|
323
|
+
localStorage.setItem("theme", theme);
|
324
|
+
}, [theme, isBrowser]);
|
325
|
+
useEffect(() => {
|
326
|
+
if (!isBrowser || todos.length === 0)
|
327
|
+
return;
|
328
|
+
localStorage.setItem("todos", JSON.stringify(todos));
|
329
|
+
}, [todos, isBrowser]);
|
330
|
+
const filteredTodos = useMemo(() => {
|
331
|
+
console.log("Filtering todos - memoized computation");
|
332
|
+
return todos.filter((todo) => {
|
333
|
+
if (taskFilter === "completed")
|
334
|
+
return todo.completed;
|
335
|
+
if (taskFilter === "active")
|
336
|
+
return !todo.completed;
|
337
|
+
if (taskFilter === "high")
|
338
|
+
return todo.priority === "high";
|
339
|
+
if (taskFilter === "medium")
|
340
|
+
return todo.priority === "medium";
|
341
|
+
if (taskFilter === "low")
|
342
|
+
return todo.priority === "low";
|
343
|
+
return true;
|
344
|
+
});
|
345
|
+
}, [todos, taskFilter]);
|
346
|
+
const todoStats = useMemo(() => {
|
347
|
+
const total = todos.length;
|
348
|
+
const completed = todos.filter((t) => t.completed).length;
|
349
|
+
const active = total - completed;
|
350
|
+
const highPriority = todos.filter((t) => t.priority === "high" && !t.completed).length;
|
351
|
+
return { total, completed, active, highPriority };
|
352
|
+
}, [todos]);
|
353
|
+
const handleToggleTodo = (id) => {
|
354
|
+
console.log("Toggling todo:", id);
|
355
|
+
setTodos((prev) => prev.map(
|
356
|
+
(todo) => todo.id === id ? { ...todo, completed: !todo.completed } : todo
|
357
|
+
));
|
358
|
+
};
|
359
|
+
const handleDeleteTodo = (id) => {
|
360
|
+
console.log("Deleting todo:", id);
|
361
|
+
setTodos((prev) => prev.filter((todo) => todo.id !== id));
|
362
|
+
};
|
363
|
+
const handleAddTodo = (text, priority) => {
|
364
|
+
if (!text || !text.trim())
|
365
|
+
return;
|
366
|
+
const newTodo = {
|
367
|
+
id: Date.now(),
|
368
|
+
text: text.trim(),
|
369
|
+
completed: false,
|
370
|
+
priority: priority || "medium"
|
371
|
+
};
|
372
|
+
setTodos((prev) => [...prev, newTodo]);
|
373
|
+
};
|
374
|
+
const addTask = () => {
|
375
|
+
if (newTask.trim()) {
|
376
|
+
setIsLoading(true);
|
377
|
+
setTimeout(() => {
|
378
|
+
handleAddTodo(newTask, taskPriority);
|
379
|
+
setNewTask("");
|
380
|
+
setIsLoading(false);
|
381
|
+
}, 300);
|
382
|
+
}
|
383
|
+
};
|
384
|
+
const handleKeyPress = (e) => {
|
385
|
+
if (e.key === "Enter") {
|
386
|
+
addTask();
|
387
|
+
}
|
388
|
+
};
|
389
|
+
const clearCompleted = () => {
|
390
|
+
setTodos((prev) => prev.filter((todo) => !todo.completed));
|
391
|
+
};
|
392
|
+
const markAllComplete = () => {
|
393
|
+
setTodos((prev) => prev.map((todo) => ({ ...todo, completed: true })));
|
394
|
+
};
|
395
|
+
const toggleTheme = () => {
|
396
|
+
setTheme((prev) => prev === "light" ? "dark" : "light");
|
397
|
+
};
|
398
|
+
const simulateError = () => {
|
399
|
+
throw new Error("Simulated error for testing error boundary");
|
400
|
+
};
|
401
|
+
if (error) {
|
402
|
+
return /* @__PURE__ */ jsx("div", { className: "error-container" }, /* @__PURE__ */ jsx("h2", null, "Something went wrong!"), /* @__PURE__ */ jsx("p", null, error.message), /* @__PURE__ */ jsx("button", { onClick: resetError, className: "btn btn-primary" }, "Try Again"));
|
403
|
+
}
|
404
|
+
return /* @__PURE__ */ jsx("div", { className: `app theme-${theme}` }, /* @__PURE__ */ jsx("header", { className: "app-header" }, /* @__PURE__ */ jsx("div", { className: "container" }, /* @__PURE__ */ jsx("h1", { className: "app-title" }, "\u{1F4DD} Todo App", /* @__PURE__ */ jsx("span", { className: "subtitle" }, "Built with Frontend Hamroun")), /* @__PURE__ */ jsx("div", { className: "header-controls" }, /* @__PURE__ */ jsx(ThemeToggleButton, { theme, onToggle: toggleTheme }), /* @__PURE__ */ jsx("div", { className: "stats" }, /* @__PURE__ */ jsx("span", { className: "stat" }, "Total: ", /* @__PURE__ */ jsx("strong", null, todoStats.total)), /* @__PURE__ */ jsx("span", { className: "stat" }, "Active: ", /* @__PURE__ */ jsx("strong", null, todoStats.active)), /* @__PURE__ */ jsx("span", { className: "stat" }, "Done: ", /* @__PURE__ */ jsx("strong", null, todoStats.completed)))))), /* @__PURE__ */ jsx("main", { className: "main-content" }, /* @__PURE__ */ jsx("div", { className: "container" }, /* @__PURE__ */ jsx("section", { className: "card add-todo-section" }, /* @__PURE__ */ jsx("h2", null, "\u2795 Add New Todo"), /* @__PURE__ */ jsx("div", { className: "add-todo-form" }, /* @__PURE__ */ jsx(
|
405
|
+
"input",
|
406
|
+
{
|
407
|
+
type: "text",
|
408
|
+
value: newTask,
|
409
|
+
onChange: (e) => setNewTask(e.target.value),
|
410
|
+
onKeyPress: handleKeyPress,
|
411
|
+
placeholder: "What needs to be done?",
|
412
|
+
className: "input todo-input",
|
413
|
+
disabled: isLoading
|
414
|
+
}
|
415
|
+
), /* @__PURE__ */ jsx(
|
416
|
+
"select",
|
417
|
+
{
|
418
|
+
value: taskPriority,
|
419
|
+
onChange: (e) => setTaskPriority(e.target.value),
|
420
|
+
className: "select priority-select",
|
421
|
+
disabled: isLoading
|
422
|
+
},
|
423
|
+
/* @__PURE__ */ jsx("option", { value: "low" }, "\u{1F7E2} Low Priority"),
|
424
|
+
/* @__PURE__ */ jsx("option", { value: "medium" }, "\u{1F7E1} Medium Priority"),
|
425
|
+
/* @__PURE__ */ jsx("option", { value: "high" }, "\u{1F534} High Priority")
|
426
|
+
), /* @__PURE__ */ jsx(
|
427
|
+
"button",
|
428
|
+
{
|
429
|
+
onClick: addTask,
|
430
|
+
className: `btn btn-primary add-btn ${isLoading ? "loading" : ""}`,
|
431
|
+
disabled: isLoading || !newTask.trim()
|
432
|
+
},
|
433
|
+
isLoading ? "\u23F3 Adding..." : "\u2795 Add Todo"
|
434
|
+
))), /* @__PURE__ */ jsx("section", { className: "card filters-section" }, /* @__PURE__ */ jsx("h2", null, "\u{1F50D} Filter Todos"), /* @__PURE__ */ jsx("div", { className: "filters" }, ["all", "active", "completed", "high", "medium", "low"].map((filterType) => /* @__PURE__ */ jsx(
|
435
|
+
"button",
|
436
|
+
{
|
437
|
+
key: filterType,
|
438
|
+
onClick: () => setTaskFilter(filterType),
|
439
|
+
className: `btn btn-sm filter-btn ${taskFilter === filterType ? "btn-primary" : "btn-outline"}`
|
440
|
+
},
|
441
|
+
filterType === "all" && "\u{1F4CB} All",
|
442
|
+
filterType === "active" && "\u23F3 Active",
|
443
|
+
filterType === "completed" && "\u2705 Completed",
|
444
|
+
filterType === "high" && "\u{1F534} High Priority",
|
445
|
+
filterType === "medium" && "\u{1F7E1} Medium Priority",
|
446
|
+
filterType === "low" && "\u{1F7E2} Low Priority"
|
447
|
+
))), /* @__PURE__ */ jsx("div", { className: "bulk-actions" }, /* @__PURE__ */ jsx(
|
448
|
+
"button",
|
449
|
+
{
|
450
|
+
onClick: markAllComplete,
|
451
|
+
className: "btn btn-success btn-sm",
|
452
|
+
disabled: todoStats.active === 0
|
453
|
+
},
|
454
|
+
"\u2705 Mark All Complete"
|
455
|
+
), /* @__PURE__ */ jsx(
|
456
|
+
"button",
|
457
|
+
{
|
458
|
+
onClick: clearCompleted,
|
459
|
+
className: "btn btn-warning btn-sm",
|
460
|
+
disabled: todoStats.completed === 0
|
461
|
+
},
|
462
|
+
"\u{1F5D1}\uFE0F Clear Completed"
|
463
|
+
))), /* @__PURE__ */ jsx("section", { className: "card todos-section" }, /* @__PURE__ */ jsx("div", { className: "section-header" }, /* @__PURE__ */ jsx("h2", null, "\u{1F4CB} Todo List"), /* @__PURE__ */ jsx("div", { className: "filter-info" }, "Showing ", /* @__PURE__ */ jsx("strong", null, filteredTodos.length), " of ", /* @__PURE__ */ jsx("strong", null, todoStats.total), " todos", taskFilter !== "all" && /* @__PURE__ */ jsx("span", { className: "filter-badge" }, taskFilter))), /* @__PURE__ */ jsx("div", { className: "todos-list" }, filteredTodos.length > 0 ? filteredTodos.map((todo) => /* @__PURE__ */ jsx(
|
464
|
+
TodoItem,
|
465
|
+
{
|
466
|
+
key: todo.id,
|
467
|
+
todo,
|
468
|
+
onToggle: handleToggleTodo,
|
469
|
+
onDelete: handleDeleteTodo
|
470
|
+
}
|
471
|
+
)) : /* @__PURE__ */ jsx("div", { className: "empty-state" }, /* @__PURE__ */ jsx("p", null, taskFilter === "all" ? "\u{1F389} No todos yet. Add one above!" : taskFilter === "completed" ? "\u{1F4DD} No completed todos yet." : taskFilter === "active" ? "\u{1F3AF} No active todos. Great job!" : `\u{1F50D} No ${taskFilter} priority todos found.`)))), /* @__PURE__ */ jsx("section", { className: "card actions-section" }, /* @__PURE__ */ jsx("h2", null, "\u2699\uFE0F Actions"), /* @__PURE__ */ jsx("div", { className: "action-buttons" }, /* @__PURE__ */ jsx("button", { onClick: simulateError, className: "btn btn-danger" }, "\u{1F4A5} Test Error Boundary"))))), /* @__PURE__ */ jsx(AppFooter, { theme, isBrowser }), /* @__PURE__ */ jsx("style", null, `
|
472
|
+
* {
|
473
|
+
margin: 0;
|
474
|
+
padding: 0;
|
475
|
+
box-sizing: border-box;
|
476
|
+
}
|
477
|
+
|
478
|
+
:root {
|
479
|
+
--primary: #3b82f6;
|
480
|
+
--primary-dark: #2563eb;
|
481
|
+
--secondary: #6b7280;
|
482
|
+
--success: #10b981;
|
483
|
+
--warning: #f59e0b;
|
484
|
+
--danger: #ef4444;
|
485
|
+
--background: #ffffff;
|
486
|
+
--surface: #f9fafb;
|
487
|
+
--text: #111827;
|
488
|
+
--text-muted: #6b7280;
|
489
|
+
--border: #e5e7eb;
|
490
|
+
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
|
491
|
+
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
|
492
|
+
}
|
493
|
+
|
494
|
+
[data-theme="dark"] {
|
495
|
+
--primary: #3b82f6;
|
496
|
+
--secondary: #9ca3af;
|
497
|
+
--success: #10b981;
|
498
|
+
--warning: #f59e0b;
|
499
|
+
--danger: #ef4444;
|
500
|
+
--background: #111827;
|
501
|
+
--surface: #1f2937;
|
502
|
+
--text: #f9fafb;
|
503
|
+
--text-muted: #9ca3af;
|
504
|
+
--border: #374151;
|
505
|
+
}
|
506
|
+
|
507
|
+
body {
|
508
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
509
|
+
line-height: 1.6;
|
510
|
+
color: var(--text);
|
511
|
+
background-color: var(--background);
|
512
|
+
transition: all 0.3s ease;
|
513
|
+
}
|
514
|
+
|
515
|
+
.app {
|
516
|
+
min-height: 100vh;
|
517
|
+
display: flex;
|
518
|
+
flex-direction: column;
|
519
|
+
}
|
520
|
+
|
521
|
+
.container {
|
522
|
+
max-width: 800px;
|
523
|
+
margin: 0 auto;
|
524
|
+
padding: 0 1rem;
|
525
|
+
}
|
526
|
+
|
527
|
+
/* Header */
|
528
|
+
.app-header {
|
529
|
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
530
|
+
color: white;
|
531
|
+
padding: 2rem 0;
|
532
|
+
box-shadow: var(--shadow-lg);
|
533
|
+
}
|
534
|
+
|
535
|
+
.app-header .container {
|
536
|
+
display: flex;
|
537
|
+
justify-content: space-between;
|
538
|
+
align-items: center;
|
539
|
+
flex-wrap: wrap;
|
540
|
+
gap: 1rem;
|
541
|
+
}
|
542
|
+
|
543
|
+
.app-title {
|
544
|
+
font-size: 2rem;
|
545
|
+
font-weight: 700;
|
546
|
+
margin: 0;
|
547
|
+
display: flex;
|
548
|
+
flex-direction: column;
|
549
|
+
gap: 0.25rem;
|
550
|
+
}
|
551
|
+
|
552
|
+
.subtitle {
|
553
|
+
font-size: 1rem;
|
554
|
+
font-weight: 400;
|
555
|
+
opacity: 0.9;
|
556
|
+
}
|
557
|
+
|
558
|
+
.header-controls {
|
559
|
+
display: flex;
|
560
|
+
align-items: center;
|
561
|
+
gap: 1rem;
|
562
|
+
}
|
563
|
+
|
564
|
+
.stats {
|
565
|
+
display: flex;
|
566
|
+
gap: 1rem;
|
567
|
+
font-size: 0.875rem;
|
568
|
+
}
|
569
|
+
|
570
|
+
.stat {
|
571
|
+
background: rgba(255, 255, 255, 0.2);
|
572
|
+
padding: 0.5rem 0.75rem;
|
573
|
+
border-radius: 0.5rem;
|
574
|
+
backdrop-filter: blur(10px);
|
575
|
+
}
|
576
|
+
|
577
|
+
/* Main content */
|
578
|
+
.main-content {
|
579
|
+
flex: 1;
|
580
|
+
padding: 2rem 0;
|
581
|
+
}
|
582
|
+
|
583
|
+
/* Cards */
|
584
|
+
.card {
|
585
|
+
background: var(--surface);
|
586
|
+
border-radius: 1rem;
|
587
|
+
padding: 1.5rem;
|
588
|
+
margin-bottom: 1.5rem;
|
589
|
+
box-shadow: var(--shadow);
|
590
|
+
border: 1px solid var(--border);
|
591
|
+
transition: transform 0.2s ease;
|
592
|
+
}
|
593
|
+
|
594
|
+
.card:hover {
|
595
|
+
transform: translateY(-2px);
|
596
|
+
}
|
597
|
+
|
598
|
+
.card h2 {
|
599
|
+
font-size: 1.25rem;
|
600
|
+
margin-bottom: 1rem;
|
601
|
+
color: var(--text);
|
602
|
+
}
|
603
|
+
|
604
|
+
/* Add Todo Form */
|
605
|
+
.add-todo-form {
|
606
|
+
display: flex;
|
607
|
+
gap: 0.75rem;
|
608
|
+
flex-wrap: wrap;
|
609
|
+
}
|
610
|
+
|
611
|
+
.todo-input {
|
612
|
+
flex: 1;
|
613
|
+
min-width: 250px;
|
614
|
+
}
|
615
|
+
|
616
|
+
.priority-select {
|
617
|
+
min-width: 150px;
|
618
|
+
}
|
619
|
+
|
620
|
+
/* Filters */
|
621
|
+
.filters {
|
622
|
+
display: flex;
|
623
|
+
gap: 0.5rem;
|
624
|
+
margin-bottom: 1rem;
|
625
|
+
flex-wrap: wrap;
|
626
|
+
}
|
627
|
+
|
628
|
+
.filter-btn {
|
629
|
+
font-size: 0.875rem;
|
630
|
+
}
|
631
|
+
|
632
|
+
.bulk-actions {
|
633
|
+
display: flex;
|
634
|
+
gap: 0.5rem;
|
635
|
+
flex-wrap: wrap;
|
636
|
+
}
|
637
|
+
|
638
|
+
/* Section Header */
|
639
|
+
.section-header {
|
640
|
+
display: flex;
|
641
|
+
justify-content: space-between;
|
642
|
+
align-items: center;
|
643
|
+
margin-bottom: 1rem;
|
644
|
+
flex-wrap: wrap;
|
645
|
+
gap: 1rem;
|
646
|
+
}
|
647
|
+
|
648
|
+
.filter-info {
|
649
|
+
font-size: 0.875rem;
|
650
|
+
color: var(--text-muted);
|
651
|
+
}
|
652
|
+
|
653
|
+
.filter-badge {
|
654
|
+
background: var(--primary);
|
655
|
+
color: white;
|
656
|
+
padding: 0.25rem 0.5rem;
|
657
|
+
border-radius: 0.25rem;
|
658
|
+
font-size: 0.75rem;
|
659
|
+
margin-left: 0.5rem;
|
660
|
+
}
|
661
|
+
|
662
|
+
/* Todos List */
|
663
|
+
.todos-list {
|
664
|
+
display: flex;
|
665
|
+
flex-direction: column;
|
666
|
+
gap: 0.75rem;
|
667
|
+
}
|
668
|
+
|
669
|
+
.todo-item {
|
670
|
+
display: flex;
|
671
|
+
align-items: center;
|
672
|
+
gap: 0.75rem;
|
673
|
+
padding: 1rem;
|
674
|
+
background: var(--background);
|
675
|
+
border-radius: 0.5rem;
|
676
|
+
border: 1px solid var(--border);
|
677
|
+
transition: all 0.2s ease;
|
678
|
+
}
|
679
|
+
|
680
|
+
.todo-item:hover {
|
681
|
+
border-color: var(--primary);
|
682
|
+
box-shadow: var(--shadow);
|
683
|
+
}
|
684
|
+
|
685
|
+
.todo-item.completed {
|
686
|
+
opacity: 0.7;
|
687
|
+
}
|
688
|
+
|
689
|
+
.todo-item.completed .todo-text {
|
690
|
+
text-decoration: line-through;
|
691
|
+
}
|
692
|
+
|
693
|
+
.todo-checkbox {
|
694
|
+
width: 1.25rem;
|
695
|
+
height: 1.25rem;
|
696
|
+
cursor: pointer;
|
697
|
+
}
|
698
|
+
|
699
|
+
.todo-text {
|
700
|
+
flex: 1;
|
701
|
+
font-size: 1rem;
|
702
|
+
}
|
703
|
+
|
704
|
+
.todo-priority {
|
705
|
+
padding: 0.25rem 0.5rem;
|
706
|
+
border-radius: 0.25rem;
|
707
|
+
font-size: 0.75rem;
|
708
|
+
font-weight: 600;
|
709
|
+
}
|
710
|
+
|
711
|
+
.priority-high {
|
712
|
+
background: #fee2e2;
|
713
|
+
color: #991b1b;
|
714
|
+
}
|
715
|
+
|
716
|
+
.priority-medium {
|
717
|
+
background: #fef3c7;
|
718
|
+
color: #92400e;
|
719
|
+
}
|
720
|
+
|
721
|
+
.priority-low {
|
722
|
+
background: #dcfce7;
|
723
|
+
color: #166534;
|
724
|
+
}
|
725
|
+
|
726
|
+
.todo-actions {
|
727
|
+
display: flex;
|
728
|
+
gap: 0.5rem;
|
729
|
+
}
|
730
|
+
|
731
|
+
/* Form elements */
|
732
|
+
.input, .select {
|
733
|
+
padding: 0.75rem;
|
734
|
+
border: 2px solid var(--border);
|
735
|
+
border-radius: 0.5rem;
|
736
|
+
font-size: 1rem;
|
737
|
+
background: var(--background);
|
738
|
+
color: var(--text);
|
739
|
+
transition: border-color 0.2s ease;
|
740
|
+
}
|
741
|
+
|
742
|
+
.input:focus, .select:focus {
|
743
|
+
outline: none;
|
744
|
+
border-color: var(--primary);
|
745
|
+
}
|
746
|
+
|
747
|
+
/* Buttons */
|
748
|
+
.btn {
|
749
|
+
display: inline-flex;
|
750
|
+
align-items: center;
|
751
|
+
justify-content: center;
|
752
|
+
gap: 0.5rem;
|
753
|
+
padding: 0.75rem 1.5rem;
|
754
|
+
border: none;
|
755
|
+
border-radius: 0.5rem;
|
756
|
+
font-size: 0.875rem;
|
757
|
+
font-weight: 500;
|
758
|
+
cursor: pointer;
|
759
|
+
transition: all 0.2s ease;
|
760
|
+
text-decoration: none;
|
761
|
+
user-select: none;
|
762
|
+
}
|
763
|
+
|
764
|
+
.btn:disabled {
|
765
|
+
opacity: 0.5;
|
766
|
+
cursor: not-allowed;
|
767
|
+
}
|
768
|
+
|
769
|
+
.btn-sm {
|
770
|
+
padding: 0.5rem 1rem;
|
771
|
+
font-size: 0.75rem;
|
772
|
+
}
|
773
|
+
|
774
|
+
.btn-primary {
|
775
|
+
background: var(--primary);
|
776
|
+
color: white;
|
777
|
+
}
|
778
|
+
|
779
|
+
.btn-primary:hover:not(:disabled) {
|
780
|
+
background: var(--primary-dark);
|
781
|
+
transform: translateY(-1px);
|
782
|
+
}
|
783
|
+
|
784
|
+
.btn-secondary {
|
785
|
+
background: var(--secondary);
|
786
|
+
color: white;
|
787
|
+
}
|
788
|
+
|
789
|
+
.btn-success {
|
790
|
+
background: var(--success);
|
791
|
+
color: white;
|
792
|
+
}
|
793
|
+
|
794
|
+
.btn-warning {
|
795
|
+
background: var(--warning);
|
796
|
+
color: white;
|
797
|
+
}
|
798
|
+
|
799
|
+
.btn-danger {
|
800
|
+
background: var(--danger);
|
801
|
+
color: white;
|
802
|
+
}
|
803
|
+
|
804
|
+
.btn-outline {
|
805
|
+
background: transparent;
|
806
|
+
color: var(--text);
|
807
|
+
border: 2px solid var(--border);
|
808
|
+
}
|
809
|
+
|
810
|
+
.btn-outline:hover:not(:disabled) {
|
811
|
+
background: var(--surface);
|
812
|
+
border-color: var(--primary);
|
813
|
+
}
|
814
|
+
|
815
|
+
.btn.loading {
|
816
|
+
position: relative;
|
817
|
+
color: transparent;
|
818
|
+
}
|
819
|
+
|
820
|
+
.btn.loading::after {
|
821
|
+
content: '';
|
822
|
+
position: absolute;
|
823
|
+
width: 1rem;
|
824
|
+
height: 1rem;
|
825
|
+
border: 2px solid transparent;
|
826
|
+
border-top: 2px solid currentColor;
|
827
|
+
border-radius: 50%;
|
828
|
+
animation: spin 1s linear infinite;
|
829
|
+
color: white;
|
830
|
+
}
|
831
|
+
|
832
|
+
@keyframes spin {
|
833
|
+
to { transform: rotate(360deg); }
|
834
|
+
}
|
835
|
+
|
836
|
+
.theme-toggle {
|
837
|
+
border-radius: 50%;
|
838
|
+
width: 3rem;
|
839
|
+
height: 3rem;
|
840
|
+
padding: 0;
|
841
|
+
font-size: 1.25rem;
|
842
|
+
background: rgba(255, 255, 255, 0.2);
|
843
|
+
color: white;
|
844
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
845
|
+
}
|
846
|
+
|
847
|
+
.theme-toggle:hover {
|
848
|
+
background: rgba(255, 255, 255, 0.3);
|
849
|
+
transform: scale(1.05);
|
850
|
+
}
|
851
|
+
|
852
|
+
/* Empty state */
|
853
|
+
.empty-state {
|
854
|
+
text-align: center;
|
855
|
+
padding: 3rem 1rem;
|
856
|
+
color: var(--text-muted);
|
857
|
+
}
|
858
|
+
|
859
|
+
.empty-state p {
|
860
|
+
font-size: 1.1rem;
|
861
|
+
}
|
862
|
+
|
863
|
+
/* Actions section */
|
864
|
+
.action-buttons {
|
865
|
+
display: flex;
|
866
|
+
gap: 1rem;
|
867
|
+
flex-wrap: wrap;
|
868
|
+
}
|
869
|
+
|
870
|
+
/* Footer */
|
871
|
+
.app-footer {
|
872
|
+
background: var(--surface);
|
873
|
+
border-top: 1px solid var(--border);
|
874
|
+
padding: 1.5rem 0;
|
875
|
+
text-align: center;
|
876
|
+
color: var(--text-muted);
|
877
|
+
font-size: 0.875rem;
|
878
|
+
}
|
879
|
+
|
880
|
+
.app-footer p {
|
881
|
+
margin: 0.25rem 0;
|
882
|
+
}
|
883
|
+
|
884
|
+
/* Error container */
|
885
|
+
.error-container {
|
886
|
+
display: flex;
|
887
|
+
flex-direction: column;
|
888
|
+
align-items: center;
|
889
|
+
justify-content: center;
|
890
|
+
min-height: 100vh;
|
891
|
+
padding: 2rem;
|
892
|
+
text-align: center;
|
893
|
+
}
|
894
|
+
|
895
|
+
.error-container h2 {
|
896
|
+
color: var(--danger);
|
897
|
+
margin-bottom: 1rem;
|
898
|
+
}
|
899
|
+
|
900
|
+
.error-container p {
|
901
|
+
color: var(--text-muted);
|
902
|
+
margin-bottom: 2rem;
|
903
|
+
}
|
904
|
+
|
905
|
+
/* Responsive design */
|
906
|
+
@media (max-width: 768px) {
|
907
|
+
.app-header .container {
|
908
|
+
flex-direction: column;
|
909
|
+
text-align: center;
|
910
|
+
}
|
911
|
+
|
912
|
+
.app-title {
|
913
|
+
font-size: 1.75rem;
|
914
|
+
}
|
915
|
+
|
916
|
+
.add-todo-form {
|
917
|
+
flex-direction: column;
|
918
|
+
}
|
919
|
+
|
920
|
+
.todo-input, .priority-select {
|
921
|
+
min-width: auto;
|
922
|
+
width: 100%;
|
923
|
+
}
|
924
|
+
|
925
|
+
.section-header {
|
926
|
+
flex-direction: column;
|
927
|
+
align-items: stretch;
|
928
|
+
}
|
929
|
+
|
930
|
+
.stats {
|
931
|
+
justify-content: center;
|
932
|
+
}
|
933
|
+
|
934
|
+
.filters {
|
935
|
+
justify-content: center;
|
936
|
+
}
|
937
|
+
|
938
|
+
.bulk-actions {
|
939
|
+
justify-content: center;
|
940
|
+
}
|
941
|
+
}
|
942
|
+
|
943
|
+
/* Smooth transitions */
|
944
|
+
* {
|
945
|
+
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
946
|
+
}
|
947
|
+
`));
|
948
|
+
}
|
949
|
+
|
950
|
+
// src/client.tsx
|
951
|
+
hydrate(jsx(App, {}), document.getElementById("root"));
|