@cocreate/utils 1.38.0 → 1.39.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/utils",
3
- "version": "1.38.0",
3
+ "version": "1.39.1",
4
4
  "description": "A simple utils component in vanilla javascript. Easily configured using HTML5 attributes and/or JavaScript API.",
5
5
  "keywords": [
6
6
  "utils",
@@ -45,14 +45,10 @@
45
45
  },
46
46
  "main": "./src/index.js",
47
47
  "devDependencies": {
48
- "@babel/core": "^7.9.6",
49
- "@babel/preset-env": "^7.9.6",
50
- "babel-loader": "^8.1.0",
51
- "clean-webpack-plugin": "^3.0.0",
52
- "file-loader": "^6.2.0",
48
+ "css-loader": "^5.1.3",
49
+ "esbuild": "^0.25.2",
50
+ "esbuild-loader": "^4.3.0",
53
51
  "mini-css-extract-plugin": "^1.5.0",
54
- "style-loader": "^3.3.1",
55
- "terser-webpack-plugin": "^5.1.1",
56
52
  "webpack": "^5.24.4",
57
53
  "webpack-cli": "^4.5.0",
58
54
  "webpack-log": "^3.0.1"
package/src/index.js CHANGED
@@ -221,6 +221,72 @@
221
221
  }
222
222
  }
223
223
 
224
+ /**
225
+ * Flattens a deeply nested object or array into a single-level object
226
+ * where keys are dot/bracket notation paths and values are the corresponding
227
+ * primitive values (string, number, boolean, null, undefined) from the
228
+ * original structure.
229
+ *
230
+ * @param {object|array} input The object or array to flatten.
231
+ * @returns {object} A flat object with dot/bracket notation keys mapped to their primitive values.
232
+ */
233
+ function objectToDotNotation(input) {
234
+ const results = {}; // Initialize an empty OBJECT to store key-value pairs
235
+
236
+ // Helper function for recursion
237
+ function traverse(currentValue, path) {
238
+ // Base Case: Primitive values (or null/undefined)
239
+ // We consider anything that's not an object or is null as a primitive endpoint.
240
+ if (typeof currentValue !== "object" || currentValue === null) {
241
+ // Only add if a path exists (handles the edge case where the initial input itself is primitive)
242
+ if (path !== undefined && path !== null && path !== "") {
243
+ results[path] = currentValue; // Assign the primitive value to the constructed path key
244
+ }
245
+ return; // Stop recursion for this branch
246
+ }
247
+
248
+ // Recursive Step: Array
249
+ if (Array.isArray(currentValue)) {
250
+ // Only traverse non-empty arrays if we're looking for primitive values
251
+ // If you wanted empty arrays represented (e.g., 'notes': []), you'd add logic here.
252
+ if (currentValue.length > 0) {
253
+ currentValue.forEach((item, index) => {
254
+ // Build the next path segment using bracket notation for arrays
255
+ const nextPath = `${path}[${index}]`;
256
+ traverse(item, nextPath);
257
+ });
258
+ } else if (path) {
259
+ // Optional: represent empty arrays explicitly if needed
260
+ // results[path] = []; // Uncomment this line if you want empty arrays included
261
+ }
262
+ }
263
+ // Recursive Step: Object (and not null, not an array)
264
+ else {
265
+ const keys = Object.keys(currentValue);
266
+ // Only traverse non-empty objects if we're looking for primitive values
267
+ // If you wanted empty objects represented (e.g., 'metadata': {}), you'd add logic here.
268
+ if (keys.length > 0) {
269
+ keys.forEach((key) => {
270
+ // Build the next path segment:
271
+ // - Use dot notation if the current path is not empty.
272
+ // - Just use the key if it's the first level.
273
+ const nextPath = path ? `${path}.${key}` : key;
274
+ traverse(currentValue[key], nextPath);
275
+ });
276
+ } else if (path) {
277
+ // Optional: represent empty objects explicitly if needed
278
+ // results[path] = {}; // Uncomment this line if you want empty objects included
279
+ }
280
+ }
281
+ }
282
+
283
+ // Start the traversal with the initial input and an empty path
284
+ // Using an empty string '' ensures the first level keys don't start with '.'
285
+ traverse(input, "");
286
+
287
+ return results; // Return the populated results object
288
+ }
289
+
224
290
  function getValueFromObject(object = {}, path = "", throwError = false) {
225
291
  try {
226
292
  if (!Object.keys(object).length || !path) {
@@ -439,6 +505,51 @@
439
505
  }
440
506
  }
441
507
 
508
+ /**
509
+ * Converts a JavaScript object into a URL-encoded query string using
510
+ * the standard URLSearchParams API (works in Node.js and modern browsers).
511
+ * - Uses repeated keys for arrays.
512
+ * - Skips null/undefined values.
513
+ * - Converts other values to strings.
514
+ *
515
+ * @param {object | null | undefined} paramsObj The object to convert.
516
+ * @returns {string} A URL-encoded query string starting with '?'
517
+ * if params exist, otherwise an empty string.
518
+ */
519
+ function objectToSearchParams(paramsObj) {
520
+ if (
521
+ !paramsObj ||
522
+ typeof paramsObj !== "object" ||
523
+ Array.isArray(paramsObj)
524
+ ) {
525
+ return "";
526
+ }
527
+
528
+ // Filter out null/undefined values
529
+ const filteredObj = {};
530
+ for (const key in paramsObj) {
531
+ if (Object.hasOwn(paramsObj, key)) {
532
+ const value = paramsObj[key];
533
+ if (value !== null && value !== undefined) {
534
+ filteredObj[key] = value;
535
+ }
536
+ }
537
+ }
538
+
539
+ if (Object.keys(filteredObj).length === 0) {
540
+ return "";
541
+ }
542
+
543
+ // --- CORE LOGIC ---
544
+ // Create URLSearchParams directly from the filtered object
545
+ // This works identically in modern Node.js and browsers.
546
+ const searchParams = new URLSearchParams(filteredObj);
547
+ const queryString = searchParams.toString();
548
+ // --- END CORE LOGIC ---
549
+
550
+ return queryString ? `?${queryString}` : "";
551
+ }
552
+
442
553
  function domParser(str) {
443
554
  try {
444
555
  var mainTag = str.match(/\<(?<tag>[a-z0-9]+)(.*?)?\>/).groups.tag;
@@ -540,196 +651,168 @@
540
651
  return path;
541
652
  }
542
653
 
654
+ // Define a list of query types that describe relationships or contexts in the DOM.
543
655
  const queryTypes = [
544
- "selector",
545
- "closest",
546
- "parent",
547
- "next",
548
- "previous",
549
- "document",
550
- "frame",
551
- "top"
656
+ "$closest", // Selects the closest ancestor matching the selector
657
+ "$parent", // Selects the direct parent element
658
+ "$next", // Selects the next sibling element
659
+ "$previous", // Selects the previous sibling element
660
+ "$document", // Selects the document containing the element
661
+ "$frame", // Selects the frame or iframe containing the element
662
+ "$top" // Selects the top-level document or window
552
663
  ];
553
664
 
554
- const queryTypesRegex = new RegExp(`\\$(?:${queryTypes.join("|")})\\b`); // Find the *first* match
665
+ // Construct a regular expression pattern to match any of the query types.
666
+ // Each query type begins with a dollar sign, which is escaped in the regex.
667
+ const regexPatternString = `(?:${queryTypes
668
+ .map((type) => type.replace("$", "\\$")) // Escape $ character for regex
669
+ .join("|")})`;
555
670
 
556
- function queryElements({ element = document, prefix, type, selector }) {
557
- let elements = new Set();
671
+ // Compile the regular expression pattern into a RegExp object.
672
+ // This regex will be used to find the first occurrence of any query type within a string.
673
+ const queryTypesRegex = new RegExp(regexPatternString);
558
674
 
559
- let hasAttribute = false;
675
+ /**
676
+ * Function to query DOM elements based on specified criteria.
677
+ * @param {Object} params - Object containing parameters for querying elements.
678
+ * @param {Element|Document} params.element - The root element or document to start the query from. Defaults to the entire document.
679
+ * @param {string} params.prefix - Optional prefix used to construct the query.
680
+ * @param {string} params.selector - The CSS selector or query string to use.
681
+ * @returns {Array} - An array of elements that match the query.
682
+ */
683
+ function queryElements({ element = document, prefix, selector }) {
684
+ // Initialize a Set to store unique elements.
685
+ let elements = new Set();
560
686
 
561
- if (!selector) {
562
- if (!prefix && element.nodeType === 1) {
687
+ // If no selector is provided and the element is an element node.
688
+ if (!selector && element.nodeType === 1) {
689
+ // If no prefix is provided, derive one from the element's attributes.
690
+ if (!prefix) {
563
691
  for (let attr of element.attributes) {
564
- let parts = attr.name.split("-");
565
- if (parts.length < 2) continue;
566
-
567
- let possibleType = parts.pop();
568
- if (queryTypes.includes(possibleType)) {
569
- type = [possibleType];
570
- prefix = parts.join("-");
571
- break;
692
+ // If an attribute with "-query" suffix is found, extract prefix.
693
+ if (attr.name.endsWith("-query")) {
694
+ prefix = attr.name.slice(0, -6);
572
695
  }
573
696
  }
697
+ // If no valid prefix is found, exit the function.
574
698
  if (!prefix) return false;
575
- } else if (!type && element.nodeType === 1) {
576
- for (let i = 0; i < queryTypes.length; i++) {
577
- if (element.hasAttribute(`${prefix}-${queryTypes[i]}`)) {
578
- type = [queryTypes[i]];
579
- }
580
- }
581
699
  }
700
+ // Get the selector using the derived prefix.
701
+ selector = element.getAttribute(prefix + "-" + "query");
702
+ if (!selector) return false; // Exit if no selector is found.
582
703
  }
583
704
 
584
- if (!type) type = selector ? ["selector"] : queryTypes;
705
+ // Split complex selectors into individual ones, handling nested structures.
706
+ let selectors = selector.split(/,(?![^()\[\]]*[)\]])/g);
707
+ for (let i = 0; i < selectors.length; i++) {
708
+ if (!selectors[i]) continue; // Skip empty selectors.
585
709
 
586
- if (!Array.isArray(type)) type = [type];
710
+ let queriedElement = element; // Start query from the current element.
587
711
 
588
- for (let i = 0; i < type.length; i++) {
589
- if (!selector && element.nodeType !== 9) {
590
- let name = prefix + "-" + type[i];
591
- if (!element.hasAttribute(name)) continue;
592
- hasAttribute = true;
593
- selector = element.getAttribute(name);
594
- type = [type[i]];
712
+ // If media queries are included, verify and filter the selector accordingly.
713
+ if (selectors[i].includes("@")) {
714
+ selectors[i] = checkMediaQueries(selectors[i]);
715
+ if (selectors[i] === false) continue; // Skip if media query is not matched.
595
716
  }
596
717
 
597
- let mainElement = element;
598
- if (
599
- [
600
- "parent",
601
- "next",
602
- "previous",
603
- "document",
604
- "frame",
605
- "top"
606
- ].includes(type[i])
607
- ) {
608
- mainElement = queryType(mainElement, type[i]);
609
- }
610
-
611
- if (!selector) {
612
- elements.add(mainElement);
613
- } else {
614
- let selectors = selector.split(/,(?![^()]*\))/g);
615
-
616
- for (let j = 0; j < selectors.length; j++) {
617
- if (!selectors[j]) continue;
618
-
619
- let queriedElement = mainElement;
620
-
621
- if (selectors[j].includes("@")) {
622
- selectors[j] = checkMediaQueries(selectors[j]);
623
- if (selectors[j] === false) continue;
624
- }
625
-
626
- if (type[i] === "closest") {
627
- let [closestSelector, remainingSelector = ""] =
628
- selectors[j].split(/\s+/, 2);
629
- queriedElement =
630
- queriedElement.closest(closestSelector);
718
+ let remainingSelector = selectors[i].trim(); // Trim any whitespace.
719
+ let match;
720
+
721
+ // Process each part of the selector that corresponds to specific query types/operators.
722
+ while ((match = queryTypesRegex.exec(remainingSelector)) !== null) {
723
+ const matchIndex = match.index;
724
+ const operator = match[0];
725
+
726
+ // Process the part before the operator (if any).
727
+ const part = remainingSelector
728
+ .substring(0, matchIndex)
729
+ .trim()
730
+ .replace(/,$/, "");
731
+ if (part) {
732
+ queriedElement = querySelector(queriedElement, part);
733
+ if (!queriedElement) break; // Exit loop if no element is found.
734
+ }
631
735
 
632
- if (!queriedElement) continue;
736
+ // Remove the processed part and operator from the remaining selector.
737
+ remainingSelector = remainingSelector
738
+ .substring(matchIndex + operator.length)
739
+ .trim();
633
740
 
634
- selectors[j] = remainingSelector;
635
- }
636
-
637
- let remainingSelector = selectors[j].trim();
638
- let match;
741
+ // Handle the $closest operator specifically.
742
+ if (operator === "$closest") {
743
+ let [closest, remaining = ""] = remainingSelector.split(
744
+ /\s+/,
745
+ 2
746
+ );
747
+ queriedElement = queriedElement.closest(closest);
748
+ remainingSelector = remaining.trim();
749
+ } else {
750
+ // Process other operators using the queryType function.
751
+ queriedElement = queryType(queriedElement, operator);
752
+ }
639
753
 
640
- while (
641
- (match = queryTypesRegex.exec(remainingSelector)) !==
642
- null
643
- ) {
644
- const matchIndex = match.index;
645
- const operator = match[0];
646
-
647
- // Process the part before the operator (if any)
648
- const part = remainingSelector
649
- .substring(0, matchIndex)
650
- .trim()
651
- .replace(/,$/, "");
652
- if (part) {
653
- queriedElement = querySelector(
654
- queriedElement,
655
- part
656
- );
657
- if (!queriedElement) break;
658
- }
754
+ if (!queriedElement) break; // Exit loop if no element is found.
755
+ }
659
756
 
660
- // Process the operator
661
- queriedElement = queryType(
662
- queriedElement,
663
- operator.substring(1)
664
- );
665
- if (!queriedElement) break;
666
-
667
- // Remove the processed part and operator from the remaining selector
668
- remainingSelector = remainingSelector
669
- .substring(matchIndex + operator.length)
670
- .trim();
671
- }
757
+ if (!queriedElement) continue; // Skip if no element is found.
672
758
 
673
- // Process the remaining part after the last operator (if any)
674
- if (remainingSelector) {
675
- queriedElement = querySelector(
676
- queriedElement,
677
- remainingSelector.trim().replace(/,$/, "")
678
- );
679
- }
759
+ // Process the remaining part after the last operator (if any).
760
+ if (remainingSelector) {
761
+ queriedElement = querySelector(
762
+ queriedElement,
763
+ remainingSelector
764
+ );
765
+ }
680
766
 
681
- if (
682
- Array.isArray(queriedElement) ||
683
- queriedElement instanceof HTMLCollection ||
684
- queriedElement instanceof NodeList
685
- ) {
686
- for (let el of queriedElement) {
687
- if (el instanceof Element) {
688
- elements.add(el);
689
- }
690
- }
691
- } else if (queriedElement instanceof Element) {
692
- elements.add(queriedElement);
767
+ // Add elements to the set.
768
+ if (
769
+ Array.isArray(queriedElement) ||
770
+ queriedElement instanceof HTMLCollection ||
771
+ queriedElement instanceof NodeList
772
+ ) {
773
+ for (let el of queriedElement) {
774
+ if (el instanceof Element) {
775
+ elements.add(el);
693
776
  }
694
777
  }
778
+ } else if (queriedElement instanceof Element) {
779
+ elements.add(queriedElement);
695
780
  }
696
781
  }
697
782
 
698
- if (!hasAttribute && !selector) {
699
- elements = false;
700
- } else {
701
- elements = Array.from(elements);
702
- }
703
-
704
- return elements;
783
+ return Array.from(elements); // Convert Set to Array and return found elements.
705
784
  }
706
785
 
707
786
  function queryType(element, type) {
708
787
  if (!element) return null;
709
788
 
710
789
  switch (type) {
711
- case "top":
790
+ case "$top":
712
791
  return window.top.document;
713
- case "frame":
714
- // If element is a document, return the iframe element containing it
792
+ case "$frame":
793
+ // If element is a document, return the iframe element containing it
715
794
  if (element.nodeType === 9) return window.frameElement;
716
- // If element is an iframe, return it as is
795
+ // If element is an iframe, return it as is
717
796
  return element;
718
- case "document":
719
- // If element is a document, return itself, else return `ownerDocument`
797
+ case "$document":
798
+ // If element is a document, return itself, else return `ownerDocument`
799
+ return element.nodeType === 9 ? element : element.ownerDocument;
800
+ case "$closest":
801
+ // If closest find the first selector seperated by space
802
+
720
803
  return element.nodeType === 9 ? element : element.ownerDocument;
721
- case "parent":
722
- // If it's a document, return the parent document (if inside an iframe)
804
+ case "$parent":
805
+ // If it's a document, return the parent document (if inside an iframe)
723
806
  if (element.nodeType === 9) {
724
807
  return element.defaultView !== window.top
725
808
  ? element.defaultView.parent.document
726
809
  : null;
727
810
  }
728
- // Otherwise, return parent element
811
+ // Otherwise, return parent element
729
812
  return element.parentElement;
730
- case "next":
813
+ case "$next":
731
814
  return element.nextElementSibling;
732
- case "previous":
815
+ case "$previous":
733
816
  return element.previousElementSibling;
734
817
  default:
735
818
  return null;
@@ -1169,7 +1252,9 @@
1169
1252
  checkValue,
1170
1253
  isValidDate,
1171
1254
  dotNotationToObject,
1255
+ objectToDotNotation,
1172
1256
  getValueFromObject,
1257
+ objectToSearchParams,
1173
1258
  domParser,
1174
1259
  parseTextToHtml,
1175
1260
  escapeHtml,
package/webpack.config.js CHANGED
@@ -1,90 +1,65 @@
1
- const path = require("path")
2
- const TerserPlugin = require("terser-webpack-plugin")
3
- const MiniCssExtractPlugin = require("mini-css-extract-plugin")
4
- const { CleanWebpackPlugin } = require("clean-webpack-plugin")
5
-
6
- module.exports = (env, argv) => {
7
- let isProduction = false
8
- if (argv.mode === 'production')
9
- isProduction = true
10
-
11
- const config = {
12
- entry: {
13
- "CoCreate-utils": "./src/index.js",
14
- },
15
- output: {
16
- path: path.resolve(__dirname, "dist"),
17
- filename: isProduction ? "[name].min.js" : "[name].js",
18
- libraryTarget: "umd",
19
- libraryExport: "default",
20
- library: ["CoCreate", "utils"],
21
- globalObject: "this",
22
- },
23
-
24
- plugins: [
25
- new CleanWebpackPlugin(),
26
- new MiniCssExtractPlugin({
27
- filename: "[name].css",
28
- }),
29
- ],
30
- // Default mode for Webpack is production.
31
- // Depending on mode Webpack will apply different things
32
- // on final bundle. For now we don't need production's JavaScript
33
- // minifying and other thing so let's set mode to development
34
- mode: isProduction ? "production" : "development",
35
- module: {
36
- rules: [
37
- {
38
- test: /.js$/,
39
- exclude: /(node_modules)/,
40
- use: {
41
- loader: "babel-loader",
42
- options: {
43
- plugins: ["@babel/plugin-transform-modules-commonjs"],
44
- },
45
- },
46
- },
47
- {
48
- test: /.css$/i,
49
- use: [
50
- { loader: "style-loader", options: { injectType: "linkTag" } },
51
- "file-loader",
52
- ],
53
- },
54
- ],
55
- },
56
-
57
- // add source map
58
- ...(isProduction ? {} : { devtool: "eval-source-map" }),
59
-
60
- optimization: {
61
- minimize: true,
62
- minimizer: [
63
- new TerserPlugin({
64
- extractComments: true,
65
- // cache: true,
66
- parallel: true,
67
- // sourceMap: true, // Must be set to true if using source-maps in production
68
- terserOptions: {
69
- // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
70
- // extractComments: 'all',
71
- compress: {
72
- drop_console: true,
73
- },
74
- },
75
- }),
76
- ],
77
- splitChunks: {
78
- chunks: "all",
79
- minSize: 200,
80
- // maxSize: 99999,
81
- //minChunks: 1,
82
-
83
- cacheGroups: {
84
- defaultVendors: false,
85
- },
86
- },
87
- },
88
- }
89
- return config
90
- }
1
+ const path = require("path");
2
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
3
+ const { EsbuildPlugin } = require("esbuild-loader");
4
+ const { FileUploader } = require("@cocreate/webpack");
5
+
6
+ module.exports = async (env, argv) => {
7
+ const isProduction = argv && argv.mode === "production";
8
+ const config = {
9
+ entry: {
10
+ "CoCreate-utils": "./src/index.js"
11
+ },
12
+ output: {
13
+ path: path.resolve(__dirname, "dist"),
14
+ filename: isProduction ? "[name].min.js" : "[name].js",
15
+ libraryExport: "default",
16
+ library: ["CoCreate", "utils"],
17
+ clean: true
18
+ },
19
+ plugins: [
20
+ new MiniCssExtractPlugin({
21
+ filename: isProduction ? "[name].min.css" : "[name].css"
22
+ }),
23
+ new FileUploader(env, argv)
24
+ ],
25
+ mode: isProduction ? "production" : "development",
26
+ devtool: isProduction ? "source-map" : "eval-source-map",
27
+ module: {
28
+ rules: [
29
+ {
30
+ test: /.js$/,
31
+ exclude: /node_modules/,
32
+ use: {
33
+ loader: "esbuild-loader",
34
+ options: {
35
+ loader: "js",
36
+ target: "es2017"
37
+ }
38
+ }
39
+ },
40
+ {
41
+ test: /.css$/i,
42
+ use: [MiniCssExtractPlugin.loader, "css-loader"]
43
+ }
44
+ ]
45
+ },
46
+ optimization: {
47
+ minimize: isProduction,
48
+ minimizer: [
49
+ new EsbuildPlugin({
50
+ target: "es2017",
51
+ css: true
52
+ })
53
+ ],
54
+ splitChunks: {
55
+ cacheGroups: {
56
+ defaultVendors: false
57
+ }
58
+ }
59
+ },
60
+ performance: {
61
+ hints: isProduction ? "warning" : false
62
+ }
63
+ };
64
+ return config;
65
+ };