@cocreate/element-prototype 1.28.1 → 1.29.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/src/index.js CHANGED
@@ -20,8 +20,16 @@
20
20
  // you must obtain a commercial license from CoCreate LLC.
21
21
  // For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
22
22
 
23
- import { getAttribute } from './getAttribute';
24
- import { setValue } from './setValue';
25
- import { getValue } from './getValue';
23
+ import { getAttribute } from "./getAttribute";
24
+ import { setValue } from "./setValue";
25
+ import { getValue } from "./getValue";
26
+ import { queryElements } from "./queryElements";
27
+ import { processOperators } from "./operators";
26
28
 
27
- export default { getAttribute, getValue, setValue }
29
+ export default {
30
+ getAttribute,
31
+ getValue,
32
+ setValue,
33
+ processOperators,
34
+ queryElements
35
+ };
@@ -0,0 +1,255 @@
1
+ import { ObjectId, queryElements } from "@cocreate/utils";
2
+
3
+ // Operators handled directly for simple, synchronous value retrieval
4
+ const customOperators = new Map(
5
+ Object.entries({
6
+ $organization_id: () => localStorage.getItem("organization_id"),
7
+ $user_id: () => localStorage.getItem("user_id"),
8
+ $clientId: () => localStorage.getItem("clientId"),
9
+ $session_id: () => localStorage.getItem("session_id"),
10
+ $value: (element) => element.getValue() || "",
11
+ $innerWidth: () => window.innerWidth,
12
+ $innerHeight: () => window.innerHeight,
13
+ $href: () => window.location.href.replace(/\/$/, ""),
14
+ $origin: () => window.location.origin,
15
+ $protocol: () => window.location.protocol,
16
+ $hostname: () => window.location.hostname,
17
+ $host: () => window.location.host,
18
+ $port: () => window.location.port,
19
+ $pathname: () => window.location.pathname.replace(/\/$/, ""),
20
+ $hash: () => window.location.hash,
21
+ $subdomain: () => getSubdomain() || "",
22
+ $object_id: () => ObjectId().toString(),
23
+ "ObjectId()": () => ObjectId().toString(),
24
+ $relativePath: () => {
25
+ let currentPath = window.location.pathname.replace(/\/[^\/]*$/, ""); // Remove file or last segment from path
26
+ let depth = currentPath.split("/").filter(Boolean).length; // Count actual directory levels
27
+ return depth > 0 ? "../".repeat(depth) : "./";
28
+ },
29
+ $path: () => {
30
+ let path = window.location.pathname;
31
+ if (path.split("/").pop().includes(".")) {
32
+ path = path.replace(/\/[^\/]+$/, "/");
33
+ }
34
+ return path === "/" ? "" : path;
35
+ },
36
+ $param: (element, args) => args,
37
+ $setValue: (element, args) => element.setValue(...args) || ""
38
+ })
39
+ );
40
+
41
+ // Operators that access a specific property of a target element
42
+ const propertyOperators = new Set([
43
+ "$scrollWidth",
44
+ "$scrollHeight",
45
+ "$offsetWidth",
46
+ "$offsetHeight",
47
+ "$id",
48
+ "$tagName",
49
+ "$className",
50
+ "$textContent",
51
+ "$innerHTML",
52
+ "$getValue"
53
+ ]);
54
+
55
+ // Combine all known operator keys for the main parsing regex
56
+ const knownOperatorKeys = [
57
+ ...customOperators.keys(),
58
+ ...propertyOperators
59
+ ].sort((a, b) => b.length - a.length);
60
+
61
+ function escapeRegexKey(key) {
62
+ if (key.startsWith("$")) {
63
+ return "\\" + key; // Escape the leading $
64
+ } else if (key === "ObjectId()") {
65
+ return "ObjectId\\(\\)"; // Escape the parentheses
66
+ }
67
+ return key; // Should not happen with current keys, but fallback
68
+ }
69
+
70
+ const operatorPattern = knownOperatorKeys.map(escapeRegexKey).join("|");
71
+
72
+ // Regex to find potential operators and their arguments
73
+ // $1: Potential operator identifier (e.g., $user_id, $closestDiv)
74
+ // $2: Arguments within parentheses (optional)
75
+ const regex = new RegExp(`(${operatorPattern})(?:\\s*\\((.*?)\\))?`, "g");
76
+
77
+ /**
78
+ * Synchronously processes a string, finding and replacing operators recursively.
79
+ * Assumes ALL underlying operations (getValue, queryElements) are synchronous.
80
+ * @param {Element | null} element - Context element.
81
+ * @param {string} value - String containing operators.
82
+ * @param {string[]} [exclude=[]] - Operator prefixes to ignore.
83
+ * @returns {string} - Processed string.
84
+ */
85
+ function processOperators(element, value, exclude = [], parent) {
86
+ // Early exit if no operators are possible or value is not a string
87
+ if (typeof value !== "string" || !value.includes("$")) {
88
+ return value;
89
+ }
90
+ let params = [];
91
+ const processedValue = value.replace(
92
+ regex,
93
+ (match, operator, args = "") => {
94
+ // 'match' is the full matched string (e.g., "$closest(.myClass)")
95
+ // 'operator' is the identifier part (e.g., "$closest")
96
+ // 'args' is the content within parentheses (e.g., ".myClass") or "" if no parentheses
97
+
98
+ if (operator === "$param" && !args) {
99
+ return match;
100
+ }
101
+
102
+ // If a valid operator was identified AND it's not in the exclude list
103
+ if (operator && !exclude.includes(operator)) {
104
+ // Resolve the value for the identified operator and its arguments
105
+ // Pass the *trimmed* arguments to the resolver
106
+ let resolvedValue = resolveOperator(
107
+ element,
108
+ operator,
109
+ args.replace(/^['"]|['"]$/g, "").trim(),
110
+ parent
111
+ );
112
+
113
+ if (operator === "$param") {
114
+ params.push(resolvedValue);
115
+ return "";
116
+ }
117
+
118
+ return resolvedValue ?? "";
119
+ } else {
120
+ // If no known operator matched, or if it was excluded,
121
+ // return the original matched string (no replacement).
122
+ return match;
123
+ }
124
+ }
125
+ );
126
+
127
+ if (params.length) {
128
+ return params;
129
+ }
130
+
131
+ return processedValue;
132
+ }
133
+
134
+ /**
135
+ /**
136
+ * Synchronously determines and executes the action for processing a single operator token.
137
+ * @param {HTMLElement|null} element - The context element from which to derive values or execute methods.
138
+ * @param {string} operator - The operator to apply, indicating what actions or property/method to evaluate.
139
+ * @param {string|Array} args - Arguments that may be used by the operator, which could be further processed if they contain a nested operator.
140
+ * @param {string} parent - Context in which the function is called, potentially affecting behavior or processing.
141
+ * @returns {string} The final resolved value after applying the operator to the given elements.
142
+ */
143
+ function resolveOperator(element, operator, args, parent) {
144
+ // If args contain any operators (indicated by '$'), process them recursively
145
+ if (args && args.includes("$")) {
146
+ // Reprocess args to resolve any nested operators
147
+ args = processOperators(element, args, "", operator);
148
+ }
149
+
150
+ // Initialize an array of elements to operate on, starting with the single element reference if provided
151
+ let targetElements = element ? [element] : [];
152
+
153
+ // If args are provided as a string, treat it as a selector to find applicable target elements
154
+ if (args && typeof args === "string") {
155
+ targetElements = queryElements({
156
+ element, // Use the context element as the base for querying
157
+ selector: args // Selector from args to find matching elements
158
+ });
159
+
160
+ // If no elements are found matching the selector in args, return args unmodified
161
+ if (!targetElements.length) return args;
162
+ }
163
+
164
+ // Generate a processed value by applying the operator to each of the target elements
165
+ let value = processValues(targetElements, operator, args, parent);
166
+
167
+ // If the result is a string and still contains unresolved operators, process them further
168
+ if (value && typeof value === "string" && value.includes("$")) {
169
+ // Resolve any remaining operators within the value string
170
+ value = processOperators(element, value, parent);
171
+ }
172
+
173
+ // Return the final processed value, fully resolved
174
+ return value;
175
+ }
176
+
177
+ /**
178
+ * Synchronously processes and aggregates values from a set of elements based on a specified operator.
179
+ * @param {Array<HTMLElement>} elements - Array of elements to be processed.
180
+ * @param {string} operator - The operator to apply to each element, indicating which property or method to use.
181
+ * @param {string|Array} args - Arguments that may be passed to the method if the operator corresponds to a function.
182
+ * @param {string} parent - Context in which the function is called, possibly influencing behavior (e.g., special handling for "$param").
183
+ * @returns {string} The combined string value obtained by processing elements with the specified operator.
184
+ */
185
+ function processValues(elements, operator, args, parent) {
186
+ // Attempt to fetch a custom operator function associated with the operator
187
+ let customOp = customOperators.get(operator);
188
+
189
+ // Initialize an empty string to accumulate results from processing each element
190
+ let aggregatedString = "";
191
+
192
+ // Iterate over each element in the provided elements array
193
+ for (const el of elements) {
194
+ // If the element is null or undefined, skip to the next iteration
195
+ if (!el) continue;
196
+
197
+ // Determine the raw value from the custom operator or by accessing a property/method directly on the element
198
+ let rawValue = customOp || el?.[operator.substring(1)];
199
+
200
+ // Check if the rawValue is a function and process it using provided arguments
201
+ if (typeof rawValue === "function") {
202
+ // If arguments are provided as an array
203
+ if (Array.isArray(args)) {
204
+ // If there are arguments, exit by returning an empty string (assumes args should not be used here)
205
+ if (args.length) {
206
+ return "";
207
+ }
208
+ // Invoke the function using the element and spread array arguments
209
+ rawValue = rawValue(el, ...args);
210
+ } else {
211
+ // Otherwise, invoke the function using the element and direct arguments
212
+ rawValue = rawValue(el, args);
213
+ }
214
+ }
215
+
216
+ // If the parent context requires parameter resolution
217
+ if (parent === "$param") {
218
+ // Return the first evaluated rawValue that is not null or undefined
219
+ if (rawValue) {
220
+ return rawValue;
221
+ }
222
+ } else {
223
+ // Otherwise, append the stringified rawValue to the aggregated result, defaulting to an empty string if it's nullish
224
+ aggregatedString += String(rawValue ?? "");
225
+ }
226
+ }
227
+
228
+ // Return the final aggregated string containing all processed values
229
+ return aggregatedString;
230
+ }
231
+
232
+ /**
233
+ * Extracts the subdomain from the current window's hostname.
234
+ * @returns {string|null} - The subdomain part of the hostname if it exists, or null if there is none.
235
+ */
236
+ function getSubdomain() {
237
+ // Retrieve the hostname from the current window's location
238
+ const hostname = window.location.hostname;
239
+
240
+ // Split the hostname into parts divided by dots ('.')
241
+ const parts = hostname.split(".");
242
+
243
+ // Check if the hostname has more than two parts and ensure the last part isn't a number (a common TLD check)
244
+ // A typical domain structure might look like "sub.domain.com",
245
+ // where "sub" is the subdomain, "domain" is the second-level domain, and "com" is the top-level domain.
246
+ if (parts.length > 2 && isNaN(parseInt(parts[parts.length - 1]))) {
247
+ // Join all parts except the last two (which are assumed to be the domain and TLD) to get the subdomain
248
+ return parts.slice(0, parts.length - 2).join(".");
249
+ }
250
+
251
+ // Return null if there's no valid subdomain structure
252
+ return null;
253
+ }
254
+
255
+ export { processOperators };
@@ -0,0 +1,18 @@
1
+ import utils from "@cocreate/utils";
2
+
3
+ /**
4
+ * Custom method to query elements in the context of a specific DOM element.
5
+ *
6
+ * @param {Object} options - Options to customize the query.
7
+ * @returns {NodeListOf<Element>} - A list of elements found based on the query criteria.
8
+ */
9
+ function queryElements(options = {}) {
10
+ // Use `this` as the element context and pass additional options to utils.queryElements
11
+ return utils.queryElements({ element: this, ...options });
12
+ }
13
+
14
+ // Attach the method to Element's prototype to allow its use as an instance method
15
+ Element.prototype.queryElements = queryElements;
16
+
17
+ // Export the function for direct use in other parts of your application
18
+ export { queryElements };
package/src/setValue.js CHANGED
@@ -19,11 +19,26 @@ const setValue = (el, value, dispatch) => {
19
19
  else if (typeof value === "object") value = JSON.stringify(value, null, 2);
20
20
 
21
21
  if (["time", "datetime", "datetime-local"].includes(el.type)) {
22
- if (el.value) {
23
- value = new Date(el.value).toLocalString();
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}`;
37
+ }
24
38
  } else {
25
- value = "";
39
+ el.value = "";
26
40
  }
41
+ return dispatchEvents(el, bubbles, dispatch);
27
42
  }
28
43
 
29
44
  let valueType = el.getAttribute("value-type");
@@ -100,6 +115,8 @@ const setValue = (el, value, dispatch) => {
100
115
  el.srcdoc = value;
101
116
  } else if (el.tagName === "SCRIPT") {
102
117
  setScript(el, value);
118
+ } else if (el.hasAttribute("value")) {
119
+ el.setAttribute("value", value);
103
120
  } else {
104
121
  if (el.hasAttribute("contenteditable") && el == document.activeElement)
105
122
  return;
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-element-prototype": "./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", "element-prototype"],
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-element-prototype": "./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", "elementPrototype"],
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
+ };
package/src/utility.js DELETED
@@ -1,80 +0,0 @@
1
- import { ObjectId } from "@cocreate/utils";
2
-
3
- function getSubdomain() {
4
- const hostname = window.location.hostname; // e.g., "api.dev.example.com"
5
- const parts = hostname.split(".");
6
-
7
- // Handle edge cases for single-word hostnames or IPs
8
- if (parts.length > 2 && isNaN(parts[parts.length - 1])) {
9
- return parts.slice(0, parts.length - 2).join("."); // Subdomain
10
- }
11
-
12
- return null; // No subdomain
13
- }
14
-
15
- const operatorsMap = {
16
- $href: function () {
17
- return window.location.href;
18
- },
19
- $origin: function () {
20
- return window.location.origin;
21
- },
22
- $protocol: function () {
23
- return window.location.protocol;
24
- },
25
- $host: function () {
26
- return window.location.host;
27
- },
28
- $hostname: function () {
29
- return window.location.hostname;
30
- },
31
- $port: function () {
32
- return window.location.port;
33
- },
34
- $pathname: function () {
35
- return window.location.pathname;
36
- },
37
- $hash: function () {
38
- return window.location.hash;
39
- },
40
- $subdomain: function () {
41
- return getSubdomain() || "";
42
- },
43
- $object_id: function () {
44
- return ObjectId().toString();
45
- }
46
- };
47
-
48
- function urlOperators(value) {
49
- if (typeof value !== "string") {
50
- console.error("Value is not a string:", value);
51
- return value; // Return as-is for non-string input
52
- }
53
-
54
- // Dynamically construct a regex from the keys in operatorsMap
55
- const operatorKeys = Object.keys(operatorsMap)
56
- .map((key) => `\\${key}`)
57
- .join("|");
58
- const regex = new RegExp(operatorKeys, "g");
59
-
60
- // Debugging regex match
61
- // if (!regex.test(value)) {
62
- // console.warn("Regex did not match any part of the input value.");
63
- // }
64
-
65
- // Replace matched operators with their resolved values
66
- const updatedValue = value.replace(regex, function (match) {
67
- if (operatorsMap[match]) {
68
- const replacement = operatorsMap[match]();
69
- console.log(`Replacing "${match}" with "${replacement}"`);
70
- return replacement || "";
71
- } else {
72
- console.warn(`No match found for "${match}" in operatorsMap.`);
73
- return ""; // Default replacement if operator not found
74
- }
75
- });
76
-
77
- return updatedValue;
78
- }
79
-
80
- export default { urlOperators };