@cocreate/element-prototype 1.30.0 → 1.31.1

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.
@@ -22,13 +22,13 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
  steps:
24
24
  - name: Checkout
25
- uses: actions/checkout@v3
25
+ uses: actions/checkout@v4
26
26
  - name: Setup Node.js
27
- uses: actions/setup-node@v3
27
+ uses: actions/setup-node@v4
28
28
  with:
29
- node-version: 14
29
+ node-version: 22 # Required for the latest semantic-release plugins
30
30
  - name: Semantic Release
31
- uses: cycjimmy/semantic-release-action@v3
31
+ uses: cycjimmy/semantic-release-action@v4 # Update to v4 for better Node 20+ support
32
32
  id: semantic
33
33
  with:
34
34
  extra_plugins: |
@@ -36,9 +36,8 @@ jobs:
36
36
  @semantic-release/git
37
37
  @semantic-release/github
38
38
  env:
39
- GITHUB_TOKEN: "${{ secrets.GITHUB }}"
39
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" # Use the built-in token if possible
40
40
  NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
41
41
  outputs:
42
42
  new_release_published: "${{ steps.semantic.outputs.new_release_published }}"
43
43
  new_release_version: "${{ steps.semantic.outputs.new_release_version }}"
44
-
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [1.31.1](https://github.com/CoCreate-app/CoCreate-element-prototype/compare/v1.31.0...v1.31.1) (2026-01-18)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * improved handling of type ([afd4027](https://github.com/CoCreate-app/CoCreate-element-prototype/commit/afd40271d6c299736ed13ff90a3ca1f98b5f0449))
7
+ * update worklow ([636b355](https://github.com/CoCreate-app/CoCreate-element-prototype/commit/636b35571bb11a30ba0310c65e3ecaf07e977476))
8
+
9
+ # [1.31.0](https://github.com/CoCreate-app/CoCreate-element-prototype/compare/v1.30.0...v1.31.0) (2025-11-16)
10
+
11
+
12
+ ### Features
13
+
14
+ * enhance date handling in getValue and add custom operators for number formatting and UID generation ([280a469](https://github.com/CoCreate-app/CoCreate-element-prototype/commit/280a469d7c8b007c4cb2ed35ac7f4645327ac25e))
15
+
1
16
  # [1.30.0](https://github.com/CoCreate-app/CoCreate-element-prototype/compare/v1.29.3...v1.30.0) (2025-10-11)
2
17
 
3
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/element-prototype",
3
- "version": "1.30.0",
3
+ "version": "1.31.1",
4
4
  "description": "A simple element-prototype component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
5
5
  "keywords": [
6
6
  "element-prototype",
package/src/getValue.js CHANGED
@@ -43,7 +43,8 @@ const getValue = (element, valueType) => {
43
43
  let suffix = element.getAttribute("value-suffix") || "";
44
44
 
45
45
  // Determine elementType using type first, fallback to tagName (both lowercase and uppercase respectively)
46
- const elementType = element.type ? element.type.toLowerCase() : null;
46
+ // const elementType = element.type ? element.type.toLowerCase() : null;
47
+ const elementType = element.getAttribute("type") || element.type
47
48
  const tagName = element.tagName.toUpperCase();
48
49
 
49
50
  // Switch statement to handle different element types and tagNames
@@ -196,80 +197,174 @@ const handleCheckbox = (element, prefix = "", suffix = "") => {
196
197
  * @param {string} valueType - Specifies the type of transformation to apply to the date/time value.
197
198
  * @returns {any} - The transformed or processed date/time value.
198
199
  */
200
+ /**
201
+ * Handles and transforms a date/time value with a 3-step pipeline:
202
+ * 1. SNAP: Adjusts date to start/end of periods (Month, Week, Year).
203
+ * 2. MATH: Adds/Subtracts Years, Months, Weeks, Days, Hours, Minutes, Seconds.
204
+ * 3. FORMAT: Returns the final string/number representation.
205
+ */
199
206
  const handleDateTime = (element, value, valueType) => {
200
- // Convert special string '$now' to current date
207
+ const inputType = (element.getAttribute("type") || element.type || "").toLowerCase();
208
+ let date;
201
209
  if (value === "$now") {
202
- value = new Date();
210
+ date = new Date();
211
+ } else if (value instanceof Date) {
212
+ // If it's already a date object, clone it to avoid mutating references
213
+ date = new Date(value.getTime());
203
214
  } else if (value) {
204
- // Initialize a new Date from the string or object
205
- value = new Date(value);
215
+ if (
216
+ typeof value === "string" &&
217
+ /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d{3})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/.test(
218
+ value
219
+ )
220
+ ) {
221
+ // Normalize datetime-local only when seconds are absent; preserve provided seconds.
222
+ if (inputType === "datetime-local" && /^[0-9T:-]{16}$/.test(value)) {
223
+ value = `${value}:00`;
224
+ }
225
+ date = new Date(value);
226
+ } else {
227
+ date = new Date(value);
228
+ }
229
+ } else {
230
+ console.warn("Provided date is invalid:", value);
231
+ return "";
206
232
  }
207
233
 
208
- // Check if value is a valid date
209
- if (value instanceof Date && !isNaN(value.getTime())) {
210
- // Default behavior if no specific valueType provided
211
- if (!valueType) {
212
- value = value.toISOString();
234
+ if (date instanceof Date && !isNaN(date.getTime())) {
235
+ // These operations "floor" or "ceil" the date to a specific boundary.
236
+ switch (valueType) {
237
+ case "startOfDay":
238
+ date.setHours(0, 0, 0, 0);
239
+ break;
240
+ case "startOfWeek":
241
+ const startWkOff = parseInt(
242
+ element.getAttribute("week-start-day") || 0,
243
+ 10
244
+ );
245
+ date.setDate(date.getDate() - date.getDay() + startWkOff);
246
+ date.setHours(0, 0, 0, 0);
247
+ break;
248
+ case "endOfWeek":
249
+ const endWkOff = parseInt(
250
+ element.getAttribute("week-start-day") || 0,
251
+ 10
252
+ );
253
+ date.setDate(date.getDate() - date.getDay() + 6 + endWkOff);
254
+ date.setHours(23, 59, 59, 999);
255
+ break;
256
+ case "startOfMonth":
257
+ date.setDate(1);
258
+ date.setHours(0, 0, 0, 0);
259
+ break;
260
+ case "endOfMonth":
261
+ date.setMonth(date.getMonth() + 1, 0);
262
+ date.setHours(23, 59, 59, 999);
263
+ break;
264
+ case "startOfYear":
265
+ date.setMonth(0, 1);
266
+ date.setHours(0, 0, 0, 0);
267
+ break;
268
+ case "endOfYear":
269
+ date.setMonth(11, 31);
270
+ date.setHours(23, 59, 59, 999);
271
+ break;
213
272
  }
214
273
 
215
- // Format for 'time' type elements
216
- if (element.type === "time" && !valueType) {
217
- value = value.substring(11, 19) + "Z";
218
- }
274
+ // --- PHASE 3: MATH (Modify the Date)
275
+ // Helper to get integer value from attribute safely
276
+ const getVal = (attr) =>
277
+ parseInt(element.getAttribute(attr) || "0", 10);
278
+
279
+ // 1. Years
280
+ const addYears = getVal("add-years") - getVal("subtract-years");
281
+ if (addYears) date.setFullYear(date.getFullYear() + addYears);
282
+
283
+ // 2. Months
284
+ const addMonths = getVal("add-months") - getVal("subtract-months");
285
+ if (addMonths) date.setMonth(date.getMonth() + addMonths);
286
+
287
+ // 3. Weeks
288
+ const addWeeks = getVal("add-weeks") - getVal("subtract-weeks");
289
+ if (addWeeks) date.setDate(date.getDate() + addWeeks * 7);
290
+
291
+ // 4. Days
292
+ const addDays = getVal("add-days") - getVal("subtract-days");
293
+ if (addDays) date.setDate(date.getDate() + addDays);
294
+
295
+ // 5. Hours
296
+ const addHours = getVal("add-hours") - getVal("subtract-hours");
297
+ if (addHours) date.setHours(date.getHours() + addHours);
298
+
299
+ // 6. Minutes
300
+ const addMinutes = getVal("add-minutes") - getVal("subtract-minutes");
301
+ if (addMinutes) date.setMinutes(date.getMinutes() + addMinutes);
302
+
303
+ // 7. Seconds
304
+ const addSeconds = getVal("add-seconds") - getVal("subtract-seconds");
305
+ if (addSeconds) date.setSeconds(date.getSeconds() + addSeconds);
306
+
307
+
308
+ // --- PHASE 4: FORMATTING (Output the Result) ---
309
+ switch (valueType) {
310
+ case "getDayName":
311
+ const days = [
312
+ "Sunday",
313
+ "Monday",
314
+ "Tuesday",
315
+ "Wednesday",
316
+ "Thursday",
317
+ "Friday",
318
+ "Saturday",
319
+ ];
320
+ return days[date.getDay()];
321
+
322
+ case "getMonthName":
323
+ const months = [
324
+ "January",
325
+ "February",
326
+ "March",
327
+ "April",
328
+ "May",
329
+ "June",
330
+ "July",
331
+ "August",
332
+ "September",
333
+ "October",
334
+ "November",
335
+ "December",
336
+ ];
337
+ return months[date.getMonth()];
338
+
339
+ case "getYear":
340
+ case "getFullYear":
341
+ return date.getFullYear();
342
+
343
+ case "toUnixTimestamp":
344
+ return Math.floor(date.getTime() / 1000);
345
+
346
+ case "toLocaleString":
347
+ const locale = element.getAttribute("locale") || "en-US";
348
+ return date.toLocaleString(locale);
349
+
350
+ default:
351
+ // Handle generic methods if specified
352
+ if (valueType && typeof date[valueType] === "function") {
353
+ return date[valueType]();
354
+ }
219
355
 
220
- if (valueType) {
221
- switch (valueType) {
222
- case "getDayName":
223
- const days = [
224
- "Sunday",
225
- "Monday",
226
- "Tuesday",
227
- "Wednesday",
228
- "Thursday",
229
- "Friday",
230
- "Saturday"
231
- ];
232
- value = days[value.getDay()];
233
- break;
234
- case "getMonthName":
235
- const months = [
236
- "January",
237
- "February",
238
- "March",
239
- "April",
240
- "May",
241
- "June",
242
- "July",
243
- "August",
244
- "September",
245
- "October",
246
- "November",
247
- "December"
248
- ];
249
- value = months[value.getMonth()];
250
- break;
251
- case "toUnixTimestamp":
252
- value = Math.floor(value.getTime() / 1000);
253
- break;
254
- case "toLocaleString":
255
- let locale = element.getAttribute("locale") || "en-US";
256
- value = value.toLocaleString(locale);
257
- break;
258
- default:
259
- if (typeof value[valueType] === "function") {
260
- value = value[valueType]();
261
- } else {
262
- console.warn(
263
- `The method ${valueType} is not a function of Date object.`
264
- );
265
- }
266
- break;
267
- }
356
+ const pad = (n) => String(n).padStart(2, "0");
357
+ if (inputType === "datetime-local") {
358
+ // Return a datetime-local compatible string with seconds to avoid invalid values
359
+ return `${date.getFullYear()}-${pad(
360
+ date.getMonth() + 1
361
+ )}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(
362
+ date.getMinutes()
363
+ )}:${pad(date.getSeconds())}`;
364
+ }
365
+ return date.toISOString();
268
366
  }
269
- } else {
270
- console.warn("Provided date is invalid or could not be parsed:", value);
271
367
  }
272
- return value;
273
368
  };
274
369
 
275
370
  /**
package/src/operators.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ObjectId, queryElements, getValueFromObject } from "@cocreate/utils";
1
+ import { ObjectId, queryElements, getValueFromObject, uid } from "@cocreate/utils";
2
2
 
3
3
  // Operators handled directly for simple, synchronous value retrieval
4
4
  const customOperators = new Map(
@@ -54,7 +54,28 @@ const customOperators = new Map(
54
54
  } catch (e) {
55
55
  return value;
56
56
  }
57
- }
57
+ },
58
+ // TODO: Implement number formatting
59
+ $numberFormat: (element, args) => {
60
+ let number = parseFloat(args[0]);
61
+ // Simple, fixed arg mapping:
62
+ // args[0] = locale (internationalization)
63
+ // args[1] = options (object)
64
+ // args[2] = number (if provided). If not provided, fall back to legacy behavior where args[0] might be the number.
65
+ if (!Array.isArray(args)) args = [args];
66
+
67
+ const locale = args[0] || undefined;
68
+ const options = args[1] || {};
69
+ const numCandidate = args[2] !== undefined ? args[2] : args[0];
70
+
71
+ number = parseFloat(numCandidate);
72
+
73
+ if (isNaN(number)) return String(numCandidate ?? "");
74
+
75
+ return new Intl.NumberFormat(locale, options).format(number);
76
+ },
77
+ $uid: (element, args) => uid(args[0]) || "",
78
+
58
79
  })
59
80
  );
60
81
 
@@ -206,19 +227,32 @@ const findInnermostFunctionCall = (expression) => {
206
227
  * @returns {{operator: string, args: string, rawContent: string, fullMatch?: string} | {operator: null, args: string, rawContent: string}}
207
228
  */
208
229
  const findInnermostOperator = (expression) => {
209
-
230
+ // Helper function to strip leading and trailing parentheses from a string
231
+ function stripParentheses(str) {
232
+ let result = str;
233
+ if (result.startsWith("(")) {
234
+ result = result.substring(1);
235
+ }
236
+ if (result.endsWith(")")) {
237
+ result = result.substring(0, result.length - 1);
238
+ }
239
+ return result;
240
+ }
241
+ let args;
242
+
210
243
  // --- 1. PRIORITY: Find Innermost FUNCTION CALL (Operator with Parentheses) ---
211
244
  const functionCall = findInnermostFunctionCall(expression);
212
245
 
213
246
  if (functionCall) {
214
- // Return the full function expression details, including the full string section
215
- return {
216
- operator: functionCall.operator,
217
- args: functionCall.args,
218
- rawContent: functionCall.args, // The content inside the parentheses (arguments)
219
- fullMatch: functionCall.fullMatch // The operator(args) string (the complete section to replace)
220
- };
221
- }
247
+ // Return the full function expression details, including the full string section
248
+ args = stripParentheses(functionCall.args);
249
+ return {
250
+ operator: functionCall.operator,
251
+ args, // Arguments without parentheses
252
+ rawContent: functionCall.args, // The content inside the parentheses (arguments)
253
+ fullMatch: functionCall.fullMatch // The operator(args) string (the complete section to replace)
254
+ };
255
+ }
222
256
 
223
257
  // --- 2. FALLBACK: Find BARE OPERATOR (e.g., $value path) ---
224
258
 
@@ -229,18 +263,22 @@ const findInnermostOperator = (expression) => {
229
263
  const innermostOperator = findBareOperatorInPath(rawContent, knownOperatorKeys);
230
264
 
231
265
  if (innermostOperator) {
232
- const operatorArgs = rawContent.substring(innermostOperator.length).trim();
233
- return {
234
- operator: innermostOperator,
235
- args: operatorArgs,
236
- rawContent: rawContent,
237
- };
238
- }
266
+ const operatorArgs = rawContent.substring(innermostOperator.length).trim();
267
+ args = stripParentheses(operatorArgs);
268
+ return {
269
+ operator: innermostOperator,
270
+ args, // Arguments without parentheses
271
+ rawContent: rawContent,
272
+ };
273
+ }
274
+
239
275
 
276
+ args = stripParentheses(rawContent);
277
+
240
278
  // Fallback if no known operator is found
241
279
  return {
242
280
  operator: null,
243
- args: rawContent,
281
+ args,
244
282
  rawContent: rawContent
245
283
  };
246
284
  };
@@ -276,6 +314,7 @@ function processOperators(
276
314
 
277
315
  let processedValue = value;
278
316
  let hasPromise = false;
317
+ let parsedValue = null
279
318
 
280
319
  while (processedValue.includes("$")) {
281
320
 
@@ -336,9 +375,18 @@ function processOperators(
336
375
  replacement = resolvedValue ?? "";
337
376
  }
338
377
 
339
- // Manually replace the matched part of the string
378
+ if (processedValue === textToReplace) {
379
+ processedValue = replacement;
380
+ break;
381
+ }
382
+
340
383
  processedValue = processedValue.replace(textToReplace, replacement);
341
384
 
385
+ if (!processedValue.includes("$")) {
386
+ // If there are still unresolved operators, we need to continue processing
387
+ break;
388
+ }
389
+
342
390
  } else {
343
391
  // If operator is excluded, we need to advance past it to avoid infinite loop
344
392
  break;
package/src/setValue.js CHANGED
@@ -18,29 +18,44 @@ const setValue = (el, value, dispatch) => {
18
18
  return storage.set(el, value);
19
19
  else if (typeof value === "object") value = JSON.stringify(value, null, 2);
20
20
 
21
- if (["time", "datetime", "datetime-local"].includes(el.type)) {
22
- if (value) {
23
- const date = new Date(value);
24
- if (el.type === "time") {
25
- // Format time as "HH:MM"
26
- const hours = String(date.getHours()).padStart(2, "0");
27
- const minutes = String(date.getMinutes()).padStart(2, "0");
28
- el.value = `${hours}:${minutes}`;
29
- } else if (el.type === "datetime-local") {
30
- // Format datetime-local as "YYYY-MM-DDTHH:MM"
31
- const year = date.getFullYear();
32
- const month = String(date.getMonth() + 1).padStart(2, "0");
33
- const day = String(date.getDate()).padStart(2, "0");
34
- const hours = String(date.getHours()).padStart(2, "0");
35
- const minutes = String(date.getMinutes()).padStart(2, "0");
36
- el.value = `${year}-${month}-${day}T${hours}:${minutes}`;
21
+ if (["date", "time", "datetime", "datetime-local"].includes(el.getAttribute("type") || el.type )) {
22
+ if (value) {
23
+ const date = new Date(value);
24
+ // If 'use-utc' is present, we shift the time forward by the timezone offset.
25
+ if (el.hasAttribute("use-utc")) {
26
+ date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
37
27
  }
38
- } else {
39
- el.value = "";
40
- }
41
- return dispatchEvents(el, bubbles, dispatch);
42
- }
43
28
 
29
+ if (el.tagName === "INPUT") {
30
+ if (!isNaN(date.getTime())) {
31
+ // We no longer need ternary operators or if/else blocks here.
32
+ const year = date.getFullYear();
33
+ const month = String(date.getMonth() + 1).padStart(2, "0");
34
+ const day = String(date.getDate()).padStart(2, "0");
35
+ const hours = String(date.getHours()).padStart(2, "0");
36
+ const minutes = String(date.getMinutes()).padStart(2, "0");
37
+
38
+ // 5. Set Value
39
+ if (el.type === "time") {
40
+ el.value = `${hours}:${minutes}`;
41
+ } else if (el.type === "date") {
42
+ el.value = `${year}-${month}-${day}`;
43
+ } else {
44
+ el.value = `${year}-${month}-${day}T${hours}:${minutes}`;
45
+ }
46
+ } else {
47
+ console.warn(`Invalid date for input ${el.name}:`, value);
48
+ el.value = "";
49
+ }
50
+ return dispatchEvents(el, bubbles, dispatch);
51
+ } else if (!isNaN(date.getTime())) {
52
+ value = date.toLocaleString()
53
+ }
54
+ } else {
55
+ el.value = "";
56
+ }
57
+ }
58
+
44
59
  let valueType = el.getAttribute("value-type");
45
60
  let prefix = el.getAttribute("value-prefix") || "";
46
61
  if (prefix) value = value.toString().replace(prefix, "");