@rettangoli/fe 0.0.13 → 1.0.0-rc1

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.
@@ -0,0 +1,124 @@
1
+ export const createRuntimeDeps = ({
2
+ baseDeps,
3
+ refs,
4
+ dispatchEvent,
5
+ store,
6
+ render,
7
+ }) => {
8
+ return {
9
+ ...baseDeps,
10
+ refs,
11
+ dispatchEvent,
12
+ store,
13
+ render,
14
+ };
15
+ };
16
+
17
+ export const createStoreActionDispatcher = ({
18
+ store,
19
+ render,
20
+ parseAndRenderFn,
21
+ }) => {
22
+ return (payload) => {
23
+ const { _event, _action } = payload;
24
+ const context = parseAndRenderFn(payload, {
25
+ _event,
26
+ });
27
+
28
+ if (!store[_action]) {
29
+ throw new Error(`[Store] Action 'store.${_action}' is not defined.`);
30
+ }
31
+
32
+ store[_action](context);
33
+ render();
34
+ };
35
+ };
36
+
37
+ export const createTransformedHandlers = ({
38
+ handlers,
39
+ deps,
40
+ parseAndRenderFn,
41
+ }) => {
42
+ const transformedHandlers = {
43
+ handleCallStoreAction: createStoreActionDispatcher({
44
+ store: deps.store,
45
+ render: deps.render,
46
+ parseAndRenderFn,
47
+ }),
48
+ };
49
+
50
+ Object.keys(handlers || {}).forEach((key) => {
51
+ transformedHandlers[key] = (payload) => {
52
+ return handlers[key](deps, payload);
53
+ };
54
+ });
55
+
56
+ return transformedHandlers;
57
+ };
58
+
59
+ export const ensureSyncBeforeMountResult = (beforeMountResult) => {
60
+ if (beforeMountResult && typeof beforeMountResult.then === "function") {
61
+ throw new Error("handleBeforeMount must be synchronous and cannot return a Promise.");
62
+ }
63
+ return beforeMountResult;
64
+ };
65
+
66
+ export const runBeforeMount = ({ handlers, deps }) => {
67
+ if (!handlers?.handleBeforeMount) {
68
+ return undefined;
69
+ }
70
+ const beforeMountResult = handlers.handleBeforeMount(deps);
71
+ return ensureSyncBeforeMountResult(beforeMountResult);
72
+ };
73
+
74
+ export const runAfterMount = ({ handlers, deps }) => {
75
+ if (!handlers?.handleAfterMount) {
76
+ return;
77
+ }
78
+ handlers.handleAfterMount(deps);
79
+ };
80
+
81
+ export const buildOnUpdateChanges = ({
82
+ attributeName,
83
+ oldValue,
84
+ newValue,
85
+ deps,
86
+ propsSchemaKeys,
87
+ toCamelCase,
88
+ normalizeAttributeValue,
89
+ }) => {
90
+ const changedProp = toCamelCase(attributeName);
91
+ const newProps = {};
92
+
93
+ propsSchemaKeys.forEach((propKey) => {
94
+ const propValue = deps.props[propKey];
95
+ if (propValue !== undefined) {
96
+ newProps[propKey] = propValue;
97
+ }
98
+ });
99
+
100
+ const oldProps = {
101
+ ...newProps,
102
+ };
103
+
104
+ const normalizedOldValue = normalizeAttributeValue(oldValue);
105
+ const normalizedNewValue = normalizeAttributeValue(newValue);
106
+
107
+ if (normalizedOldValue === undefined) {
108
+ delete oldProps[changedProp];
109
+ } else {
110
+ oldProps[changedProp] = normalizedOldValue;
111
+ }
112
+
113
+ if (normalizedNewValue === undefined) {
114
+ delete newProps[changedProp];
115
+ } else {
116
+ newProps[changedProp] = normalizedNewValue;
117
+ }
118
+
119
+ return {
120
+ changedProp,
121
+ oldProps,
122
+ newProps,
123
+ };
124
+ };
@@ -0,0 +1,40 @@
1
+ import { isObjectPayload } from "./payload.js";
2
+
3
+ export const bindMethods = (element, methods) => {
4
+ if (!methods || typeof methods !== "object") {
5
+ return;
6
+ }
7
+
8
+ Object.entries(methods).forEach(([methodName, methodFn]) => {
9
+ if (methodName === "default") {
10
+ throw new Error(
11
+ "[Methods] Invalid method name 'default'. Use named exports in .methods.js; default export is not supported.",
12
+ );
13
+ }
14
+
15
+ if (typeof methodFn !== "function") {
16
+ return;
17
+ }
18
+
19
+ if (methodName in element) {
20
+ throw new Error(
21
+ `[Methods] Cannot define method '${methodName}' because it already exists on the component instance.`,
22
+ );
23
+ }
24
+
25
+ Object.defineProperty(element, methodName, {
26
+ configurable: true,
27
+ enumerable: false,
28
+ writable: false,
29
+ value: (payload = {}) => {
30
+ const normalizedPayload = payload === undefined ? {} : payload;
31
+ if (!isObjectPayload(normalizedPayload)) {
32
+ throw new Error(
33
+ `[Methods] Method '${methodName}' expects payload to be an object.`,
34
+ );
35
+ }
36
+ return methodFn.call(element, normalizedPayload);
37
+ },
38
+ });
39
+ });
40
+ };
@@ -0,0 +1,3 @@
1
+ export const isObjectPayload = (value) => {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ };
@@ -0,0 +1,79 @@
1
+ export const toKebabCase = (value) => {
2
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
3
+ };
4
+
5
+ export const toCamelCase = (value) => {
6
+ return value.replace(/-([a-z0-9])/g, (_, chr) => chr.toUpperCase());
7
+ };
8
+
9
+ export const normalizeAttributeValue = (value) => {
10
+ if (value === null || value === undefined) {
11
+ return undefined;
12
+ }
13
+ return value === "" ? true : value;
14
+ };
15
+
16
+ export const readPropFallbackFromAttributes = (source, propName) => {
17
+ const directAttrValue = source.getAttribute(propName);
18
+ if (directAttrValue !== null) {
19
+ return normalizeAttributeValue(directAttrValue);
20
+ }
21
+ const kebabPropName = toKebabCase(propName);
22
+ if (kebabPropName !== propName) {
23
+ const kebabAttrValue = source.getAttribute(kebabPropName);
24
+ if (kebabAttrValue !== null) {
25
+ return normalizeAttributeValue(kebabAttrValue);
26
+ }
27
+ }
28
+ return undefined;
29
+ };
30
+
31
+ export const createPropsProxy = (source, allowedKeys) => {
32
+ const allowed = new Set(allowedKeys);
33
+ return new Proxy(
34
+ {},
35
+ {
36
+ get(_, prop) {
37
+ if (typeof prop === "string" && allowed.has(prop)) {
38
+ const propValue = source[prop];
39
+ if (propValue !== undefined) {
40
+ return propValue;
41
+ }
42
+ return readPropFallbackFromAttributes(source, prop);
43
+ }
44
+ return undefined;
45
+ },
46
+ set() {
47
+ throw new Error("Cannot assign to read-only proxy");
48
+ },
49
+ defineProperty() {
50
+ throw new Error("Cannot define properties on read-only proxy");
51
+ },
52
+ deleteProperty() {
53
+ throw new Error("Cannot delete properties from read-only proxy");
54
+ },
55
+ has(_, prop) {
56
+ return typeof prop === "string" && allowed.has(prop);
57
+ },
58
+ ownKeys() {
59
+ return [...allowed];
60
+ },
61
+ getOwnPropertyDescriptor(_, prop) {
62
+ if (typeof prop === "string" && allowed.has(prop)) {
63
+ return {
64
+ configurable: true,
65
+ enumerable: true,
66
+ get: () => {
67
+ const propValue = source[prop];
68
+ if (propValue !== undefined) {
69
+ return propValue;
70
+ }
71
+ return readPropFallbackFromAttributes(source, prop);
72
+ },
73
+ };
74
+ }
75
+ return undefined;
76
+ },
77
+ },
78
+ );
79
+ };
@@ -0,0 +1,70 @@
1
+ import { createRefMatchers, resolveBestRefMatcher } from "../view/refs.js";
2
+
3
+ export const createRuntimeRefMatchers = (refs) => createRefMatchers(refs);
4
+
5
+ const getVNodeClassNames = (vNode) => {
6
+ const classNames = [];
7
+
8
+ const classObject = vNode?.data?.class;
9
+ if (classObject && typeof classObject === "object") {
10
+ Object.entries(classObject).forEach(([className, enabled]) => {
11
+ if (enabled) {
12
+ classNames.push(className);
13
+ }
14
+ });
15
+ }
16
+
17
+ const classAttr = vNode?.data?.attrs?.class;
18
+ if (typeof classAttr === "string") {
19
+ classAttr
20
+ .split(/\s+/)
21
+ .filter(Boolean)
22
+ .forEach((className) => classNames.push(className));
23
+ }
24
+
25
+ return [...new Set(classNames)];
26
+ };
27
+
28
+ export const matchesConfiguredRef = ({ id, classNames = [], refMatchers }) => {
29
+ if (refMatchers.length === 0) {
30
+ return false;
31
+ }
32
+
33
+ return Boolean(resolveBestRefMatcher({
34
+ elementIdForRefs: id,
35
+ classNames,
36
+ refMatchers,
37
+ }));
38
+ };
39
+
40
+ export const collectRefElements = ({ rootVNode, refs }) => {
41
+ const ids = {};
42
+ const refMatchers = createRuntimeRefMatchers(refs);
43
+
44
+ const findRefElements = (vNode) => {
45
+ if (!vNode || typeof vNode !== "object") {
46
+ return;
47
+ }
48
+
49
+ const id = vNode?.data?.attrs?.id;
50
+ const classNames = getVNodeClassNames(vNode);
51
+ const bestMatchRef = resolveBestRefMatcher({
52
+ elementIdForRefs: id,
53
+ classNames,
54
+ refMatchers,
55
+ });
56
+
57
+ if (vNode.elm && bestMatchRef) {
58
+ const key = id || bestMatchRef.refKey;
59
+ ids[key] = vNode.elm;
60
+ }
61
+
62
+ if (Array.isArray(vNode.children)) {
63
+ vNode.children.forEach(findRefElements);
64
+ }
65
+ };
66
+
67
+ findRefElements(rootVNode);
68
+
69
+ return ids;
70
+ };
@@ -0,0 +1,42 @@
1
+ import { produce } from "immer";
2
+
3
+ import { isObjectPayload } from "./payload.js";
4
+
5
+ export const bindStore = (store, props, constants) => {
6
+ const { createInitialState, ...selectorsAndActions } = store;
7
+ const selectors = {};
8
+ const actions = {};
9
+ let currentState = {};
10
+
11
+ if (createInitialState) {
12
+ currentState = createInitialState({ props, constants });
13
+ }
14
+
15
+ Object.entries(selectorsAndActions).forEach(([key, fn]) => {
16
+ if (key.startsWith("select")) {
17
+ selectors[key] = (...args) => {
18
+ return fn({ state: currentState, props, constants }, ...args);
19
+ };
20
+ return;
21
+ }
22
+
23
+ actions[key] = (payload = {}) => {
24
+ const normalizedPayload = payload === undefined ? {} : payload;
25
+ if (!isObjectPayload(normalizedPayload)) {
26
+ throw new Error(
27
+ `[Store] Action '${key}' expects payload to be an object.`,
28
+ );
29
+ }
30
+ currentState = produce(currentState, (draft) => {
31
+ return fn({ state: draft, props, constants }, normalizedPayload);
32
+ });
33
+ return currentState;
34
+ };
35
+ });
36
+
37
+ return {
38
+ getState: () => currentState,
39
+ ...actions,
40
+ ...selectors,
41
+ };
42
+ };
@@ -0,0 +1,26 @@
1
+ export const validateSchemaContract = ({ schema, methodExports = [] }) => {
2
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
3
+ throw new Error("RTGL-SCHEMA-001: componentName is required.");
4
+ }
5
+
6
+ if (typeof schema.componentName !== "string" || schema.componentName.trim() === "") {
7
+ throw new Error("RTGL-SCHEMA-001: componentName is required.");
8
+ }
9
+
10
+ if (Object.prototype.hasOwnProperty.call(schema, "attrsSchema")) {
11
+ throw new Error("RTGL-SCHEMA-002: attrsSchema is not supported.");
12
+ }
13
+
14
+ if (Array.isArray(schema.methods)) {
15
+ for (const method of schema.methods) {
16
+ if (!method || typeof method.name !== "string" || method.name.trim() === "") {
17
+ continue;
18
+ }
19
+ if (!methodExports.includes(method.name)) {
20
+ throw new Error(`RTGL-SCHEMA-003: method '${method.name}' missing in .methods.js exports.`);
21
+ }
22
+ }
23
+ }
24
+
25
+ return true;
26
+ };
@@ -0,0 +1,44 @@
1
+ export const yamlToCss = (_elementName, styleObject) => {
2
+ if (!styleObject || typeof styleObject !== "object") {
3
+ return "";
4
+ }
5
+
6
+ let css = ``;
7
+ const convertPropertiesToCss = (properties) => {
8
+ return Object.entries(properties)
9
+ .map(([property, value]) => ` ${property}: ${value};`)
10
+ .join("\n");
11
+ };
12
+
13
+ const processSelector = (selector, rules) => {
14
+ if (typeof rules !== "object" || rules === null) {
15
+ return "";
16
+ }
17
+
18
+ if (selector.startsWith("@")) {
19
+ const nestedCss = Object.entries(rules)
20
+ .map(([nestedSelector, nestedRules]) => {
21
+ const nestedProperties = convertPropertiesToCss(nestedRules);
22
+ return ` ${nestedSelector} {\n${nestedProperties
23
+ .split("\n")
24
+ .map((line) => (line ? ` ${line}` : ""))
25
+ .join("\n")}\n }`;
26
+ })
27
+ .join("\n");
28
+
29
+ return `${selector} {\n${nestedCss}\n}`;
30
+ }
31
+
32
+ const properties = convertPropertiesToCss(rules);
33
+ return `${selector} {\n${properties}\n}`;
34
+ };
35
+
36
+ Object.entries(styleObject).forEach(([selector, rules]) => {
37
+ const selectorCss = processSelector(selector, rules);
38
+ if (selectorCss) {
39
+ css += (css ? "\n\n" : "") + selectorCss;
40
+ }
41
+ });
42
+
43
+ return css;
44
+ };
@@ -0,0 +1,189 @@
1
+ const PROP_PREFIX = ":";
2
+
3
+ const lodashGet = (obj, path) => {
4
+ if (!path) return obj;
5
+
6
+ const parts = [];
7
+ let current = "";
8
+ let inBrackets = false;
9
+ let quoteChar = null;
10
+
11
+ for (let i = 0; i < path.length; i++) {
12
+ const char = path[i];
13
+
14
+ if (!inBrackets && char === ".") {
15
+ if (current) {
16
+ parts.push(current);
17
+ current = "";
18
+ }
19
+ } else if (!inBrackets && char === "[") {
20
+ if (current) {
21
+ parts.push(current);
22
+ current = "";
23
+ }
24
+ inBrackets = true;
25
+ } else if (inBrackets && char === "]") {
26
+ if (current) {
27
+ if (
28
+ (current.startsWith('"') && current.endsWith('"'))
29
+ || (current.startsWith("'") && current.endsWith("'"))
30
+ ) {
31
+ parts.push(current.slice(1, -1));
32
+ } else {
33
+ const numValue = Number(current);
34
+ parts.push(Number.isNaN(numValue) ? current : numValue);
35
+ }
36
+ current = "";
37
+ }
38
+ inBrackets = false;
39
+ quoteChar = null;
40
+ } else if (inBrackets && (char === '"' || char === "'")) {
41
+ if (!quoteChar) {
42
+ quoteChar = char;
43
+ } else if (char === quoteChar) {
44
+ quoteChar = null;
45
+ }
46
+ current += char;
47
+ } else {
48
+ current += char;
49
+ }
50
+ }
51
+
52
+ if (current) {
53
+ parts.push(current);
54
+ }
55
+
56
+ return parts.reduce((acc, part) => acc && acc[part], obj);
57
+ };
58
+
59
+ export const toCamelCase = (value) => {
60
+ return value.replace(/-([a-z0-9])/g, (_, chr) => chr.toUpperCase());
61
+ };
62
+
63
+ export const parseNodeBindings = ({
64
+ attrsString = "",
65
+ viewData = {},
66
+ tagName,
67
+ isWebComponent,
68
+ }) => {
69
+ const attrs = {};
70
+ const props = {};
71
+ const assertSupportedBooleanToggleAttr = (attrName) => {
72
+ if (
73
+ attrName === "role"
74
+ || attrName.startsWith("aria-")
75
+ || attrName.startsWith("data-")
76
+ ) {
77
+ throw new Error(
78
+ `[Parser] Invalid boolean attribute '?${attrName}'. Use normal binding for value-carrying attributes such as aria-*, data-*, and role.`,
79
+ );
80
+ }
81
+ };
82
+
83
+ const setComponentProp = (rawPropName, propValue, sourceLabel) => {
84
+ const normalizedPropName = toCamelCase(rawPropName);
85
+ if (!normalizedPropName) {
86
+ throw new Error(`[Parser] Invalid ${sourceLabel} prop name on '${tagName}'.`);
87
+ }
88
+ if (Object.prototype.hasOwnProperty.call(props, normalizedPropName)) {
89
+ throw new Error(
90
+ `[Parser] Duplicate prop binding '${normalizedPropName}' on '${tagName}'. Use only one of 'name=value' or ':name=value'.`,
91
+ );
92
+ }
93
+ props[normalizedPropName] = propValue;
94
+ };
95
+
96
+ if (!attrsString) {
97
+ return { attrs, props };
98
+ }
99
+
100
+ const attrRegex = /(\S+?)=(?:\"([^\"]*)\"|\'([^\']*)\'|([^\s]+))/g;
101
+ let match;
102
+ const processedAttrs = new Set();
103
+
104
+ while ((match = attrRegex.exec(attrsString)) !== null) {
105
+ const rawBindingName = match[1];
106
+ const rawValue = match[2] || match[3] || match[4];
107
+ processedAttrs.add(rawBindingName);
108
+
109
+ if (rawBindingName.startsWith(".")) {
110
+ attrs[rawBindingName] = rawValue;
111
+ continue;
112
+ }
113
+
114
+ if (rawBindingName.startsWith(PROP_PREFIX)) {
115
+ const propName = rawBindingName.substring(1);
116
+ let propValue = rawValue;
117
+ if (match[4] !== undefined) {
118
+ const valuePathName = match[4];
119
+ const resolvedPathValue = lodashGet(viewData, valuePathName);
120
+ if (resolvedPathValue !== undefined) {
121
+ propValue = resolvedPathValue;
122
+ }
123
+ }
124
+ setComponentProp(propName, propValue, "property-form");
125
+ continue;
126
+ }
127
+
128
+ if (rawBindingName.startsWith("?")) {
129
+ const attrName = rawBindingName.substring(1);
130
+ const attrValue = rawValue;
131
+ assertSupportedBooleanToggleAttr(attrName);
132
+
133
+ let evalValue;
134
+ if (attrValue === "true") {
135
+ evalValue = true;
136
+ } else if (attrValue === "false") {
137
+ evalValue = false;
138
+ } else {
139
+ evalValue = lodashGet(viewData, attrValue);
140
+ }
141
+
142
+ if (evalValue) {
143
+ attrs[attrName] = "";
144
+ }
145
+ if (isWebComponent && attrName !== "id") {
146
+ setComponentProp(attrName, !!evalValue, "boolean attribute-form");
147
+ }
148
+ continue;
149
+ }
150
+
151
+ attrs[rawBindingName] = rawValue;
152
+ if (isWebComponent && rawBindingName !== "id") {
153
+ setComponentProp(rawBindingName, rawValue, "attribute-form");
154
+ }
155
+ }
156
+
157
+ let remainingAttrsString = attrsString;
158
+ const processedMatches = [];
159
+ let tempMatch;
160
+ const tempAttrRegex = /(\S+?)=(?:\"([^\"]*)\"|\'([^\']*)\'|([^\s]+))/g;
161
+ while ((tempMatch = tempAttrRegex.exec(attrsString)) !== null) {
162
+ processedMatches.push(tempMatch[0]);
163
+ }
164
+
165
+ processedMatches.forEach((processedMatch) => {
166
+ remainingAttrsString = remainingAttrsString.replace(processedMatch, " ");
167
+ });
168
+
169
+ const booleanAttrRegex = /\b(\S+?)(?=\s|$)/g;
170
+ let boolMatch;
171
+ while ((boolMatch = booleanAttrRegex.exec(remainingAttrsString)) !== null) {
172
+ const attrName = boolMatch[1];
173
+ if (attrName.startsWith(".")) {
174
+ continue;
175
+ }
176
+ if (
177
+ !processedAttrs.has(attrName)
178
+ && !attrName.startsWith(PROP_PREFIX)
179
+ && !attrName.includes("=")
180
+ ) {
181
+ attrs[attrName] = "";
182
+ if (isWebComponent && attrName !== "id") {
183
+ setComponentProp(attrName, true, "boolean attribute-form");
184
+ }
185
+ }
186
+ }
187
+
188
+ return { attrs, props };
189
+ };