attrs-in-props 3.8.0 → 3.8.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/README.md +58 -1
- package/dist/cjs/index.js +108 -3
- package/dist/esm/index.js +109 -4
- package/dist/iife/index.js +110 -4
- package/index.js +155 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -74,8 +74,65 @@ const resolved = executeAttr(componentDef, element)
|
|
|
74
74
|
// -> { src: '/resolved-path.jpg', alt: 'Photo' }
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
## ARIA Attributes
|
|
78
|
+
|
|
79
|
+
All `aria-*` attributes are valid on any element. Three syntax forms are supported:
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
// 1. Kebab-case (standard HTML)
|
|
83
|
+
{ 'aria-label': 'Close', 'aria-expanded': true }
|
|
84
|
+
|
|
85
|
+
// 2. camelCase (JS-friendly) — auto-converted to kebab-case
|
|
86
|
+
{ ariaLabel: 'Close', ariaExpanded: true }
|
|
87
|
+
|
|
88
|
+
// 3. Object shorthand
|
|
89
|
+
{ aria: { label: 'Close', expanded: true, hidden: false } }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
All three produce `aria-label="Close"`, `aria-expanded="true"` in the DOM.
|
|
93
|
+
|
|
94
|
+
## Data Attributes
|
|
95
|
+
|
|
96
|
+
All `data-*` attributes are valid on any element, with the same three syntax forms:
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
{ 'data-testid': 'btn' } // kebab-case
|
|
100
|
+
{ dataTestId: 'btn' } // camelCase → data-test-id
|
|
101
|
+
{ data: { testId: 'btn' } } // object shorthand → data-test-id
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Conditional Attributes
|
|
105
|
+
|
|
106
|
+
Attributes inside `$`, `.`, `!` prefix blocks are conditionally applied — same prefixes as css-in-props:
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
const Button = {
|
|
110
|
+
// $ prefix: global case from context.cases
|
|
111
|
+
'$isSafari': { disabled: true, 'aria-label': 'Safari' },
|
|
112
|
+
|
|
113
|
+
// . prefix: truthy (props/state first, then context.cases)
|
|
114
|
+
'.isActive': { aria: { expanded: true }, 'data-state': 'open' },
|
|
115
|
+
|
|
116
|
+
// ! prefix: falsy
|
|
117
|
+
'!isActive': { ariaHidden: true }
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Conditional attribute values are wrapped in functions and re-evaluated on every `update()` call.
|
|
122
|
+
|
|
123
|
+
### `extractConditionalAttrs(props, tag, cssProps?)`
|
|
124
|
+
|
|
125
|
+
Extract HTML attributes from conditional blocks in props.
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
import { extractConditionalAttrs } from 'attrs-in-props'
|
|
129
|
+
|
|
130
|
+
const conditionalAttrs = extractConditionalAttrs(props, 'button', cssPropsRegistry)
|
|
131
|
+
// Returns attr functions that evaluate conditions on each call
|
|
132
|
+
```
|
|
133
|
+
|
|
77
134
|
## Default attributes
|
|
78
135
|
|
|
79
136
|
All HTML elements support these attributes by default: `id`, `title`, `class`, `style`, `dir`, `lang`, `hidden`, `tabindex`, `draggable`, `contenteditable`, `spellcheck`, `translate`, `role`, `slot`, and more.
|
|
80
137
|
|
|
81
|
-
Element-specific attributes are supported for 50+ HTML tags including `a`, `img`, `input`, `video`, `iframe`, `form`, `select`, `textarea`, `svg
|
|
138
|
+
All `aria-*` and `data-*` attributes are valid on any element. Element-specific attributes are supported for 50+ HTML tags including `a`, `img`, `input`, `video`, `iframe`, `form`, `select`, `textarea`, and `svg`.
|
package/dist/cjs/index.js
CHANGED
|
@@ -26,7 +26,9 @@ __export(index_exports, {
|
|
|
26
26
|
checkAttributeByTagName: () => checkAttributeByTagName,
|
|
27
27
|
checkEventFunctions: () => checkEventFunctions,
|
|
28
28
|
executeAttr: () => executeAttr,
|
|
29
|
+
extractConditionalAttrs: () => extractConditionalAttrs,
|
|
29
30
|
filterAttributesByTagName: () => filterAttributesByTagName,
|
|
31
|
+
resolveFileSource: () => resolveFileSource,
|
|
30
32
|
resolvePropValue: () => resolvePropValue
|
|
31
33
|
});
|
|
32
34
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -992,7 +994,18 @@ const DOM_EVENTS = [
|
|
|
992
994
|
"onfullscreenchange",
|
|
993
995
|
"onfullscreenerror"
|
|
994
996
|
];
|
|
997
|
+
const camelToAttr = (key) => {
|
|
998
|
+
if (key.startsWith("aria") && key.length > 4 && key.charCodeAt(4) >= 65 && key.charCodeAt(4) <= 90) {
|
|
999
|
+
return "aria-" + key.charAt(4).toLowerCase() + key.slice(5).replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1000
|
+
}
|
|
1001
|
+
if (key.startsWith("data") && key.length > 4 && key.charCodeAt(4) >= 65 && key.charCodeAt(4) <= 90) {
|
|
1002
|
+
return "data-" + key.charAt(4).toLowerCase() + key.slice(5).replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1003
|
+
}
|
|
1004
|
+
return null;
|
|
1005
|
+
};
|
|
995
1006
|
const checkAttributeByTagName = (tag, attribute) => {
|
|
1007
|
+
if (attribute.startsWith("aria-") || attribute.startsWith("data-")) return true;
|
|
1008
|
+
if (camelToAttr(attribute)) return true;
|
|
996
1009
|
if (Object.prototype.hasOwnProperty.call(HTML_ATTRIBUTES, tag)) {
|
|
997
1010
|
const attributes = HTML_ATTRIBUTES[tag];
|
|
998
1011
|
return attributes.includes(attribute) || attributes.includes("default");
|
|
@@ -1011,10 +1024,28 @@ const filterAttributesByTagName = (tag, props, cssProps) => {
|
|
|
1011
1024
|
for (const key in props) {
|
|
1012
1025
|
if (Object.prototype.hasOwnProperty.call(props, key)) {
|
|
1013
1026
|
if (cssProps && key in cssProps) continue;
|
|
1027
|
+
if (key === "aria" && props[key] && typeof props[key] === "object") {
|
|
1028
|
+
for (const ariaKey in props[key]) {
|
|
1029
|
+
if ((0, import_utils.isDefined)(props[key][ariaKey])) {
|
|
1030
|
+
filteredObject["aria-" + ariaKey] = props[key][ariaKey];
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
if (key === "data" && props[key] && typeof props[key] === "object") {
|
|
1036
|
+
for (const dataKey in props[key]) {
|
|
1037
|
+
if ((0, import_utils.isDefined)(props[key][dataKey])) {
|
|
1038
|
+
const kebab = dataKey.replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1039
|
+
filteredObject["data-" + kebab] = props[key][dataKey];
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1014
1044
|
const isAttribute = checkAttributeByTagName(tag, key);
|
|
1015
1045
|
const isEvent = checkEventFunctions(key);
|
|
1016
1046
|
if ((0, import_utils.isDefined)(props[key]) && (isAttribute || isEvent)) {
|
|
1017
|
-
|
|
1047
|
+
const attrName = camelToAttr(key) || key;
|
|
1048
|
+
filteredObject[attrName] = props[key];
|
|
1018
1049
|
}
|
|
1019
1050
|
}
|
|
1020
1051
|
}
|
|
@@ -1037,11 +1068,26 @@ const resolvePropValue = (el, value) => {
|
|
|
1037
1068
|
}
|
|
1038
1069
|
return resolved;
|
|
1039
1070
|
};
|
|
1071
|
+
const resolveFileSource = (el, value) => {
|
|
1072
|
+
let src = (el.props.preSrc || "") + (resolvePropValue(el, value) || "");
|
|
1073
|
+
if (!src) return;
|
|
1074
|
+
try {
|
|
1075
|
+
new URL(src);
|
|
1076
|
+
return src;
|
|
1077
|
+
} catch (e) {
|
|
1078
|
+
}
|
|
1079
|
+
const { context } = el;
|
|
1080
|
+
if (!context.files) return src;
|
|
1081
|
+
const fileSrc = src.startsWith("/files/") ? src.slice(7) : src;
|
|
1082
|
+
const file = context.files[src] || context.files[fileSrc];
|
|
1083
|
+
if (file && file.content) return file.content.src;
|
|
1084
|
+
return src;
|
|
1085
|
+
};
|
|
1040
1086
|
const ATTR_TRANSFORMS = {
|
|
1041
|
-
src: (el) =>
|
|
1087
|
+
src: (el) => resolveFileSource(el, el.props.src),
|
|
1042
1088
|
href: (el) => resolvePropValue(el, el.props.href),
|
|
1043
1089
|
action: (el) => resolvePropValue(el, el.props.action),
|
|
1044
|
-
poster: (el) =>
|
|
1090
|
+
poster: (el) => resolveFileSource(el, el.props.poster),
|
|
1045
1091
|
data: (el) => resolvePropValue(el, el.props.data)
|
|
1046
1092
|
};
|
|
1047
1093
|
const applyAttrTransforms = (element) => {
|
|
@@ -1056,3 +1102,62 @@ const applyAttrTransforms = (element) => {
|
|
|
1056
1102
|
}
|
|
1057
1103
|
return result;
|
|
1058
1104
|
};
|
|
1105
|
+
const resolveCase = (caseKey, element) => {
|
|
1106
|
+
const caseFn = element.context?.cases?.[caseKey];
|
|
1107
|
+
if (caseFn === void 0) return void 0;
|
|
1108
|
+
if ((0, import_utils.isFunction)(caseFn)) return caseFn.call(element, element);
|
|
1109
|
+
return !!caseFn;
|
|
1110
|
+
};
|
|
1111
|
+
const evaluateCondition = (prefix, caseKey, element) => {
|
|
1112
|
+
if (prefix === "$") {
|
|
1113
|
+
let result = resolveCase(caseKey, element);
|
|
1114
|
+
if (result === void 0) result = !!element.props?.[caseKey];
|
|
1115
|
+
return result;
|
|
1116
|
+
}
|
|
1117
|
+
let isTruthy = element.props[caseKey] === true || element.state[caseKey] || element[caseKey];
|
|
1118
|
+
if (!isTruthy) {
|
|
1119
|
+
const caseResult = resolveCase(caseKey, element);
|
|
1120
|
+
if (caseResult !== void 0) isTruthy = caseResult;
|
|
1121
|
+
}
|
|
1122
|
+
return prefix === "." ? !!isTruthy : !isTruthy;
|
|
1123
|
+
};
|
|
1124
|
+
const CONDITIONAL_PREFIXES = /* @__PURE__ */ new Set(["$", ".", "!"]);
|
|
1125
|
+
const extractConditionalAttrs = (props, tag, cssProps) => {
|
|
1126
|
+
const result = {};
|
|
1127
|
+
const addConditionalAttr = (attrName, attrVal, prefix, caseKey) => {
|
|
1128
|
+
const capturedVal = attrVal;
|
|
1129
|
+
result[attrName] = (el) => {
|
|
1130
|
+
if (!evaluateCondition(prefix, caseKey, el)) return void 0;
|
|
1131
|
+
return (0, import_utils.isFunction)(capturedVal) ? capturedVal(el) : capturedVal;
|
|
1132
|
+
};
|
|
1133
|
+
};
|
|
1134
|
+
for (const key in props) {
|
|
1135
|
+
const prefix = key.charAt(0);
|
|
1136
|
+
if (!CONDITIONAL_PREFIXES.has(prefix)) continue;
|
|
1137
|
+
const block = props[key];
|
|
1138
|
+
if (!block || typeof block !== "object") continue;
|
|
1139
|
+
const caseKey = key.slice(1);
|
|
1140
|
+
for (const attrKey in block) {
|
|
1141
|
+
if (cssProps && attrKey in cssProps) continue;
|
|
1142
|
+
if (attrKey === "aria" && block[attrKey] && typeof block[attrKey] === "object") {
|
|
1143
|
+
for (const ariaKey in block[attrKey]) {
|
|
1144
|
+
addConditionalAttr("aria-" + ariaKey, block[attrKey][ariaKey], prefix, caseKey);
|
|
1145
|
+
}
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
if (attrKey === "data" && block[attrKey] && typeof block[attrKey] === "object") {
|
|
1149
|
+
for (const dataKey in block[attrKey]) {
|
|
1150
|
+
const kebab = dataKey.replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1151
|
+
addConditionalAttr("data-" + kebab, block[attrKey][dataKey], prefix, caseKey);
|
|
1152
|
+
}
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
const isAttribute = checkAttributeByTagName(tag, attrKey);
|
|
1156
|
+
const isEvent = checkEventFunctions(attrKey);
|
|
1157
|
+
if (!isAttribute && !isEvent) continue;
|
|
1158
|
+
const attrName = camelToAttr(attrKey) || attrKey;
|
|
1159
|
+
addConditionalAttr(attrName, block[attrKey], prefix, caseKey);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return result;
|
|
1163
|
+
};
|
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDefined, isString } from "@domql/utils";
|
|
1
|
+
import { isDefined, isFunction, isString } from "@domql/utils";
|
|
2
2
|
const ARIA_ROLES = [
|
|
3
3
|
"alert",
|
|
4
4
|
"alertdialog",
|
|
@@ -960,7 +960,18 @@ const DOM_EVENTS = [
|
|
|
960
960
|
"onfullscreenchange",
|
|
961
961
|
"onfullscreenerror"
|
|
962
962
|
];
|
|
963
|
+
const camelToAttr = (key) => {
|
|
964
|
+
if (key.startsWith("aria") && key.length > 4 && key.charCodeAt(4) >= 65 && key.charCodeAt(4) <= 90) {
|
|
965
|
+
return "aria-" + key.charAt(4).toLowerCase() + key.slice(5).replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
966
|
+
}
|
|
967
|
+
if (key.startsWith("data") && key.length > 4 && key.charCodeAt(4) >= 65 && key.charCodeAt(4) <= 90) {
|
|
968
|
+
return "data-" + key.charAt(4).toLowerCase() + key.slice(5).replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
969
|
+
}
|
|
970
|
+
return null;
|
|
971
|
+
};
|
|
963
972
|
const checkAttributeByTagName = (tag, attribute) => {
|
|
973
|
+
if (attribute.startsWith("aria-") || attribute.startsWith("data-")) return true;
|
|
974
|
+
if (camelToAttr(attribute)) return true;
|
|
964
975
|
if (Object.prototype.hasOwnProperty.call(HTML_ATTRIBUTES, tag)) {
|
|
965
976
|
const attributes = HTML_ATTRIBUTES[tag];
|
|
966
977
|
return attributes.includes(attribute) || attributes.includes("default");
|
|
@@ -979,10 +990,28 @@ const filterAttributesByTagName = (tag, props, cssProps) => {
|
|
|
979
990
|
for (const key in props) {
|
|
980
991
|
if (Object.prototype.hasOwnProperty.call(props, key)) {
|
|
981
992
|
if (cssProps && key in cssProps) continue;
|
|
993
|
+
if (key === "aria" && props[key] && typeof props[key] === "object") {
|
|
994
|
+
for (const ariaKey in props[key]) {
|
|
995
|
+
if (isDefined(props[key][ariaKey])) {
|
|
996
|
+
filteredObject["aria-" + ariaKey] = props[key][ariaKey];
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
if (key === "data" && props[key] && typeof props[key] === "object") {
|
|
1002
|
+
for (const dataKey in props[key]) {
|
|
1003
|
+
if (isDefined(props[key][dataKey])) {
|
|
1004
|
+
const kebab = dataKey.replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1005
|
+
filteredObject["data-" + kebab] = props[key][dataKey];
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
982
1010
|
const isAttribute = checkAttributeByTagName(tag, key);
|
|
983
1011
|
const isEvent = checkEventFunctions(key);
|
|
984
1012
|
if (isDefined(props[key]) && (isAttribute || isEvent)) {
|
|
985
|
-
|
|
1013
|
+
const attrName = camelToAttr(key) || key;
|
|
1014
|
+
filteredObject[attrName] = props[key];
|
|
986
1015
|
}
|
|
987
1016
|
}
|
|
988
1017
|
}
|
|
@@ -1005,11 +1034,26 @@ const resolvePropValue = (el, value) => {
|
|
|
1005
1034
|
}
|
|
1006
1035
|
return resolved;
|
|
1007
1036
|
};
|
|
1037
|
+
const resolveFileSource = (el, value) => {
|
|
1038
|
+
let src = (el.props.preSrc || "") + (resolvePropValue(el, value) || "");
|
|
1039
|
+
if (!src) return;
|
|
1040
|
+
try {
|
|
1041
|
+
new URL(src);
|
|
1042
|
+
return src;
|
|
1043
|
+
} catch (e) {
|
|
1044
|
+
}
|
|
1045
|
+
const { context } = el;
|
|
1046
|
+
if (!context.files) return src;
|
|
1047
|
+
const fileSrc = src.startsWith("/files/") ? src.slice(7) : src;
|
|
1048
|
+
const file = context.files[src] || context.files[fileSrc];
|
|
1049
|
+
if (file && file.content) return file.content.src;
|
|
1050
|
+
return src;
|
|
1051
|
+
};
|
|
1008
1052
|
const ATTR_TRANSFORMS = {
|
|
1009
|
-
src: (el) =>
|
|
1053
|
+
src: (el) => resolveFileSource(el, el.props.src),
|
|
1010
1054
|
href: (el) => resolvePropValue(el, el.props.href),
|
|
1011
1055
|
action: (el) => resolvePropValue(el, el.props.action),
|
|
1012
|
-
poster: (el) =>
|
|
1056
|
+
poster: (el) => resolveFileSource(el, el.props.poster),
|
|
1013
1057
|
data: (el) => resolvePropValue(el, el.props.data)
|
|
1014
1058
|
};
|
|
1015
1059
|
const applyAttrTransforms = (element) => {
|
|
@@ -1024,6 +1068,65 @@ const applyAttrTransforms = (element) => {
|
|
|
1024
1068
|
}
|
|
1025
1069
|
return result;
|
|
1026
1070
|
};
|
|
1071
|
+
const resolveCase = (caseKey, element) => {
|
|
1072
|
+
const caseFn = element.context?.cases?.[caseKey];
|
|
1073
|
+
if (caseFn === void 0) return void 0;
|
|
1074
|
+
if (isFunction(caseFn)) return caseFn.call(element, element);
|
|
1075
|
+
return !!caseFn;
|
|
1076
|
+
};
|
|
1077
|
+
const evaluateCondition = (prefix, caseKey, element) => {
|
|
1078
|
+
if (prefix === "$") {
|
|
1079
|
+
let result = resolveCase(caseKey, element);
|
|
1080
|
+
if (result === void 0) result = !!element.props?.[caseKey];
|
|
1081
|
+
return result;
|
|
1082
|
+
}
|
|
1083
|
+
let isTruthy = element.props[caseKey] === true || element.state[caseKey] || element[caseKey];
|
|
1084
|
+
if (!isTruthy) {
|
|
1085
|
+
const caseResult = resolveCase(caseKey, element);
|
|
1086
|
+
if (caseResult !== void 0) isTruthy = caseResult;
|
|
1087
|
+
}
|
|
1088
|
+
return prefix === "." ? !!isTruthy : !isTruthy;
|
|
1089
|
+
};
|
|
1090
|
+
const CONDITIONAL_PREFIXES = /* @__PURE__ */ new Set(["$", ".", "!"]);
|
|
1091
|
+
const extractConditionalAttrs = (props, tag, cssProps) => {
|
|
1092
|
+
const result = {};
|
|
1093
|
+
const addConditionalAttr = (attrName, attrVal, prefix, caseKey) => {
|
|
1094
|
+
const capturedVal = attrVal;
|
|
1095
|
+
result[attrName] = (el) => {
|
|
1096
|
+
if (!evaluateCondition(prefix, caseKey, el)) return void 0;
|
|
1097
|
+
return isFunction(capturedVal) ? capturedVal(el) : capturedVal;
|
|
1098
|
+
};
|
|
1099
|
+
};
|
|
1100
|
+
for (const key in props) {
|
|
1101
|
+
const prefix = key.charAt(0);
|
|
1102
|
+
if (!CONDITIONAL_PREFIXES.has(prefix)) continue;
|
|
1103
|
+
const block = props[key];
|
|
1104
|
+
if (!block || typeof block !== "object") continue;
|
|
1105
|
+
const caseKey = key.slice(1);
|
|
1106
|
+
for (const attrKey in block) {
|
|
1107
|
+
if (cssProps && attrKey in cssProps) continue;
|
|
1108
|
+
if (attrKey === "aria" && block[attrKey] && typeof block[attrKey] === "object") {
|
|
1109
|
+
for (const ariaKey in block[attrKey]) {
|
|
1110
|
+
addConditionalAttr("aria-" + ariaKey, block[attrKey][ariaKey], prefix, caseKey);
|
|
1111
|
+
}
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (attrKey === "data" && block[attrKey] && typeof block[attrKey] === "object") {
|
|
1115
|
+
for (const dataKey in block[attrKey]) {
|
|
1116
|
+
const kebab = dataKey.replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1117
|
+
addConditionalAttr("data-" + kebab, block[attrKey][dataKey], prefix, caseKey);
|
|
1118
|
+
}
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
const isAttribute = checkAttributeByTagName(tag, attrKey);
|
|
1122
|
+
const isEvent = checkEventFunctions(attrKey);
|
|
1123
|
+
if (!isAttribute && !isEvent) continue;
|
|
1124
|
+
const attrName = camelToAttr(attrKey) || attrKey;
|
|
1125
|
+
addConditionalAttr(attrName, block[attrKey], prefix, caseKey);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return result;
|
|
1129
|
+
};
|
|
1027
1130
|
export {
|
|
1028
1131
|
ARIA_ROLES,
|
|
1029
1132
|
ATTR_TRANSFORMS,
|
|
@@ -1033,6 +1136,8 @@ export {
|
|
|
1033
1136
|
checkAttributeByTagName,
|
|
1034
1137
|
checkEventFunctions,
|
|
1035
1138
|
executeAttr,
|
|
1139
|
+
extractConditionalAttrs,
|
|
1036
1140
|
filterAttributesByTagName,
|
|
1141
|
+
resolveFileSource,
|
|
1037
1142
|
resolvePropValue
|
|
1038
1143
|
};
|
package/dist/iife/index.js
CHANGED
|
@@ -37,10 +37,11 @@ var AttrsInProps = (() => {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
// ../utils/dist/esm/types.js
|
|
40
|
-
var isString, isDefined;
|
|
40
|
+
var isString, isFunction, isDefined;
|
|
41
41
|
var init_types = __esm({
|
|
42
42
|
"../utils/dist/esm/types.js"() {
|
|
43
43
|
isString = (arg) => typeof arg === "string";
|
|
44
|
+
isFunction = (arg) => typeof arg === "function";
|
|
44
45
|
isDefined = (arg) => arg !== void 0;
|
|
45
46
|
}
|
|
46
47
|
});
|
|
@@ -296,7 +297,9 @@ var AttrsInProps = (() => {
|
|
|
296
297
|
checkAttributeByTagName: () => checkAttributeByTagName,
|
|
297
298
|
checkEventFunctions: () => checkEventFunctions,
|
|
298
299
|
executeAttr: () => executeAttr,
|
|
300
|
+
extractConditionalAttrs: () => extractConditionalAttrs,
|
|
299
301
|
filterAttributesByTagName: () => filterAttributesByTagName,
|
|
302
|
+
resolveFileSource: () => resolveFileSource,
|
|
300
303
|
resolvePropValue: () => resolvePropValue
|
|
301
304
|
});
|
|
302
305
|
init_esm();
|
|
@@ -1261,7 +1264,18 @@ var AttrsInProps = (() => {
|
|
|
1261
1264
|
"onfullscreenchange",
|
|
1262
1265
|
"onfullscreenerror"
|
|
1263
1266
|
];
|
|
1267
|
+
var camelToAttr = (key) => {
|
|
1268
|
+
if (key.startsWith("aria") && key.length > 4 && key.charCodeAt(4) >= 65 && key.charCodeAt(4) <= 90) {
|
|
1269
|
+
return "aria-" + key.charAt(4).toLowerCase() + key.slice(5).replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1270
|
+
}
|
|
1271
|
+
if (key.startsWith("data") && key.length > 4 && key.charCodeAt(4) >= 65 && key.charCodeAt(4) <= 90) {
|
|
1272
|
+
return "data-" + key.charAt(4).toLowerCase() + key.slice(5).replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1273
|
+
}
|
|
1274
|
+
return null;
|
|
1275
|
+
};
|
|
1264
1276
|
var checkAttributeByTagName = (tag, attribute) => {
|
|
1277
|
+
if (attribute.startsWith("aria-") || attribute.startsWith("data-")) return true;
|
|
1278
|
+
if (camelToAttr(attribute)) return true;
|
|
1265
1279
|
if (Object.prototype.hasOwnProperty.call(HTML_ATTRIBUTES, tag)) {
|
|
1266
1280
|
const attributes = HTML_ATTRIBUTES[tag];
|
|
1267
1281
|
return attributes.includes(attribute) || attributes.includes("default");
|
|
@@ -1280,10 +1294,28 @@ var AttrsInProps = (() => {
|
|
|
1280
1294
|
for (const key in props) {
|
|
1281
1295
|
if (Object.prototype.hasOwnProperty.call(props, key)) {
|
|
1282
1296
|
if (cssProps && key in cssProps) continue;
|
|
1297
|
+
if (key === "aria" && props[key] && typeof props[key] === "object") {
|
|
1298
|
+
for (const ariaKey in props[key]) {
|
|
1299
|
+
if (isDefined(props[key][ariaKey])) {
|
|
1300
|
+
filteredObject["aria-" + ariaKey] = props[key][ariaKey];
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
if (key === "data" && props[key] && typeof props[key] === "object") {
|
|
1306
|
+
for (const dataKey in props[key]) {
|
|
1307
|
+
if (isDefined(props[key][dataKey])) {
|
|
1308
|
+
const kebab = dataKey.replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1309
|
+
filteredObject["data-" + kebab] = props[key][dataKey];
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1283
1314
|
const isAttribute = checkAttributeByTagName(tag, key);
|
|
1284
1315
|
const isEvent = checkEventFunctions(key);
|
|
1285
1316
|
if (isDefined(props[key]) && (isAttribute || isEvent)) {
|
|
1286
|
-
|
|
1317
|
+
const attrName = camelToAttr(key) || key;
|
|
1318
|
+
filteredObject[attrName] = props[key];
|
|
1287
1319
|
}
|
|
1288
1320
|
}
|
|
1289
1321
|
}
|
|
@@ -1306,11 +1338,26 @@ var AttrsInProps = (() => {
|
|
|
1306
1338
|
}
|
|
1307
1339
|
return resolved;
|
|
1308
1340
|
};
|
|
1341
|
+
var resolveFileSource = (el, value) => {
|
|
1342
|
+
let src = (el.props.preSrc || "") + (resolvePropValue(el, value) || "");
|
|
1343
|
+
if (!src) return;
|
|
1344
|
+
try {
|
|
1345
|
+
new URL(src);
|
|
1346
|
+
return src;
|
|
1347
|
+
} catch (e) {
|
|
1348
|
+
}
|
|
1349
|
+
const { context } = el;
|
|
1350
|
+
if (!context.files) return src;
|
|
1351
|
+
const fileSrc = src.startsWith("/files/") ? src.slice(7) : src;
|
|
1352
|
+
const file = context.files[src] || context.files[fileSrc];
|
|
1353
|
+
if (file && file.content) return file.content.src;
|
|
1354
|
+
return src;
|
|
1355
|
+
};
|
|
1309
1356
|
var ATTR_TRANSFORMS = {
|
|
1310
|
-
src: (el) =>
|
|
1357
|
+
src: (el) => resolveFileSource(el, el.props.src),
|
|
1311
1358
|
href: (el) => resolvePropValue(el, el.props.href),
|
|
1312
1359
|
action: (el) => resolvePropValue(el, el.props.action),
|
|
1313
|
-
poster: (el) =>
|
|
1360
|
+
poster: (el) => resolveFileSource(el, el.props.poster),
|
|
1314
1361
|
data: (el) => resolvePropValue(el, el.props.data)
|
|
1315
1362
|
};
|
|
1316
1363
|
var applyAttrTransforms = (element) => {
|
|
@@ -1325,6 +1372,65 @@ var AttrsInProps = (() => {
|
|
|
1325
1372
|
}
|
|
1326
1373
|
return result;
|
|
1327
1374
|
};
|
|
1375
|
+
var resolveCase = (caseKey, element) => {
|
|
1376
|
+
const caseFn = element.context?.cases?.[caseKey];
|
|
1377
|
+
if (caseFn === void 0) return void 0;
|
|
1378
|
+
if (isFunction(caseFn)) return caseFn.call(element, element);
|
|
1379
|
+
return !!caseFn;
|
|
1380
|
+
};
|
|
1381
|
+
var evaluateCondition = (prefix, caseKey, element) => {
|
|
1382
|
+
if (prefix === "$") {
|
|
1383
|
+
let result = resolveCase(caseKey, element);
|
|
1384
|
+
if (result === void 0) result = !!element.props?.[caseKey];
|
|
1385
|
+
return result;
|
|
1386
|
+
}
|
|
1387
|
+
let isTruthy = element.props[caseKey] === true || element.state[caseKey] || element[caseKey];
|
|
1388
|
+
if (!isTruthy) {
|
|
1389
|
+
const caseResult = resolveCase(caseKey, element);
|
|
1390
|
+
if (caseResult !== void 0) isTruthy = caseResult;
|
|
1391
|
+
}
|
|
1392
|
+
return prefix === "." ? !!isTruthy : !isTruthy;
|
|
1393
|
+
};
|
|
1394
|
+
var CONDITIONAL_PREFIXES = /* @__PURE__ */ new Set(["$", ".", "!"]);
|
|
1395
|
+
var extractConditionalAttrs = (props, tag, cssProps) => {
|
|
1396
|
+
const result = {};
|
|
1397
|
+
const addConditionalAttr = (attrName, attrVal, prefix, caseKey) => {
|
|
1398
|
+
const capturedVal = attrVal;
|
|
1399
|
+
result[attrName] = (el) => {
|
|
1400
|
+
if (!evaluateCondition(prefix, caseKey, el)) return void 0;
|
|
1401
|
+
return isFunction(capturedVal) ? capturedVal(el) : capturedVal;
|
|
1402
|
+
};
|
|
1403
|
+
};
|
|
1404
|
+
for (const key in props) {
|
|
1405
|
+
const prefix = key.charAt(0);
|
|
1406
|
+
if (!CONDITIONAL_PREFIXES.has(prefix)) continue;
|
|
1407
|
+
const block = props[key];
|
|
1408
|
+
if (!block || typeof block !== "object") continue;
|
|
1409
|
+
const caseKey = key.slice(1);
|
|
1410
|
+
for (const attrKey in block) {
|
|
1411
|
+
if (cssProps && attrKey in cssProps) continue;
|
|
1412
|
+
if (attrKey === "aria" && block[attrKey] && typeof block[attrKey] === "object") {
|
|
1413
|
+
for (const ariaKey in block[attrKey]) {
|
|
1414
|
+
addConditionalAttr("aria-" + ariaKey, block[attrKey][ariaKey], prefix, caseKey);
|
|
1415
|
+
}
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
if (attrKey === "data" && block[attrKey] && typeof block[attrKey] === "object") {
|
|
1419
|
+
for (const dataKey in block[attrKey]) {
|
|
1420
|
+
const kebab = dataKey.replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
1421
|
+
addConditionalAttr("data-" + kebab, block[attrKey][dataKey], prefix, caseKey);
|
|
1422
|
+
}
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
const isAttribute = checkAttributeByTagName(tag, attrKey);
|
|
1426
|
+
const isEvent = checkEventFunctions(attrKey);
|
|
1427
|
+
if (!isAttribute && !isEvent) continue;
|
|
1428
|
+
const attrName = camelToAttr(attrKey) || attrKey;
|
|
1429
|
+
addConditionalAttr(attrName, block[attrKey], prefix, caseKey);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
return result;
|
|
1433
|
+
};
|
|
1328
1434
|
return __toCommonJS(index_exports);
|
|
1329
1435
|
})();
|
|
1330
1436
|
// @preserve-env
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
import { isDefined, isString } from '@domql/utils'
|
|
3
|
+
import { isDefined, isFunction, isString } from '@domql/utils'
|
|
4
4
|
|
|
5
5
|
export const ARIA_ROLES = [
|
|
6
6
|
'alert',
|
|
@@ -1040,7 +1040,23 @@ export const DOM_EVENTS = [
|
|
|
1040
1040
|
'onfullscreenerror'
|
|
1041
1041
|
]
|
|
1042
1042
|
|
|
1043
|
+
// Convert camelCase aria/data attrs to kebab-case: ariaLabel → aria-label, dataTestId → data-test-id
|
|
1044
|
+
const camelToAttr = (key) => {
|
|
1045
|
+
if (key.startsWith('aria') && key.length > 4 && key.charCodeAt(4) >= 65 && key.charCodeAt(4) <= 90) {
|
|
1046
|
+
return 'aria-' + key.charAt(4).toLowerCase() + key.slice(5).replace(/([A-Z])/g, (m) => '-' + m.toLowerCase())
|
|
1047
|
+
}
|
|
1048
|
+
if (key.startsWith('data') && key.length > 4 && key.charCodeAt(4) >= 65 && key.charCodeAt(4) <= 90) {
|
|
1049
|
+
return 'data-' + key.charAt(4).toLowerCase() + key.slice(5).replace(/([A-Z])/g, (m) => '-' + m.toLowerCase())
|
|
1050
|
+
}
|
|
1051
|
+
return null
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1043
1054
|
export const checkAttributeByTagName = (tag, attribute) => {
|
|
1055
|
+
// aria-* and data-* are valid on all elements
|
|
1056
|
+
if (attribute.startsWith('aria-') || attribute.startsWith('data-')) return true
|
|
1057
|
+
// camelCase aria/data attrs
|
|
1058
|
+
if (camelToAttr(attribute)) return true
|
|
1059
|
+
|
|
1044
1060
|
if (Object.prototype.hasOwnProperty.call(HTML_ATTRIBUTES, tag)) {
|
|
1045
1061
|
const attributes = HTML_ATTRIBUTES[tag]
|
|
1046
1062
|
return attributes.includes(attribute) || attributes.includes('default')
|
|
@@ -1062,10 +1078,33 @@ export const filterAttributesByTagName = (tag, props, cssProps) => {
|
|
|
1062
1078
|
for (const key in props) {
|
|
1063
1079
|
if (Object.prototype.hasOwnProperty.call(props, key)) {
|
|
1064
1080
|
if (cssProps && key in cssProps) continue
|
|
1081
|
+
|
|
1082
|
+
// aria: { label: 'foo', expanded: true } → aria-label, aria-expanded
|
|
1083
|
+
if (key === 'aria' && props[key] && typeof props[key] === 'object') {
|
|
1084
|
+
for (const ariaKey in props[key]) {
|
|
1085
|
+
if (isDefined(props[key][ariaKey])) {
|
|
1086
|
+
filteredObject['aria-' + ariaKey] = props[key][ariaKey]
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
continue
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// data: { testId: 'foo' } → data-test-id
|
|
1093
|
+
if (key === 'data' && props[key] && typeof props[key] === 'object') {
|
|
1094
|
+
for (const dataKey in props[key]) {
|
|
1095
|
+
if (isDefined(props[key][dataKey])) {
|
|
1096
|
+
const kebab = dataKey.replace(/([A-Z])/g, (m) => '-' + m.toLowerCase())
|
|
1097
|
+
filteredObject['data-' + kebab] = props[key][dataKey]
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
continue
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1065
1103
|
const isAttribute = checkAttributeByTagName(tag, key)
|
|
1066
1104
|
const isEvent = checkEventFunctions(key)
|
|
1067
1105
|
if (isDefined(props[key]) && (isAttribute || isEvent)) {
|
|
1068
|
-
|
|
1106
|
+
const attrName = camelToAttr(key) || key
|
|
1107
|
+
filteredObject[attrName] = props[key]
|
|
1069
1108
|
}
|
|
1070
1109
|
}
|
|
1071
1110
|
}
|
|
@@ -1096,16 +1135,38 @@ export const resolvePropValue = (el, value) => {
|
|
|
1096
1135
|
return resolved
|
|
1097
1136
|
}
|
|
1098
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* Resolve a file URL from context.files.
|
|
1140
|
+
* Handles absolute URLs (passthrough), /files/ prefix stripping,
|
|
1141
|
+
* and file lookup for bundler-resolved paths.
|
|
1142
|
+
*/
|
|
1143
|
+
export const resolveFileSource = (el, value) => {
|
|
1144
|
+
let src = (el.props.preSrc || '') + (resolvePropValue(el, value) || '')
|
|
1145
|
+
if (!src) return
|
|
1146
|
+
|
|
1147
|
+
try { new URL(src); return src } catch (e) { } // absolute URL — passthrough
|
|
1148
|
+
|
|
1149
|
+
const { context } = el
|
|
1150
|
+
if (!context.files) return src
|
|
1151
|
+
|
|
1152
|
+
const fileSrc = src.startsWith('/files/') ? src.slice(7) : src
|
|
1153
|
+
const file = context.files[src] || context.files[fileSrc]
|
|
1154
|
+
if (file && file.content) return file.content.src
|
|
1155
|
+
|
|
1156
|
+
return src
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1099
1159
|
/**
|
|
1100
1160
|
* Auto-resolve attribute transformers.
|
|
1101
1161
|
* Attributes listed here are automatically resolved from props
|
|
1102
1162
|
* via resolvePropValue (exec + template literal replacement).
|
|
1163
|
+
* src and poster also resolve from context.files for media elements.
|
|
1103
1164
|
*/
|
|
1104
1165
|
export const ATTR_TRANSFORMS = {
|
|
1105
|
-
src: (el) =>
|
|
1166
|
+
src: (el) => resolveFileSource(el, el.props.src),
|
|
1106
1167
|
href: (el) => resolvePropValue(el, el.props.href),
|
|
1107
1168
|
action: (el) => resolvePropValue(el, el.props.action),
|
|
1108
|
-
poster: (el) =>
|
|
1169
|
+
poster: (el) => resolveFileSource(el, el.props.poster),
|
|
1109
1170
|
data: (el) => resolvePropValue(el, el.props.data)
|
|
1110
1171
|
}
|
|
1111
1172
|
|
|
@@ -1125,3 +1186,93 @@ export const applyAttrTransforms = (element) => {
|
|
|
1125
1186
|
}
|
|
1126
1187
|
return result
|
|
1127
1188
|
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Resolve a case value from context.cases.
|
|
1192
|
+
* Returns true/false, or undefined if case is not defined.
|
|
1193
|
+
*/
|
|
1194
|
+
const resolveCase = (caseKey, element) => {
|
|
1195
|
+
const caseFn = element.context?.cases?.[caseKey]
|
|
1196
|
+
if (caseFn === undefined) return undefined
|
|
1197
|
+
if (isFunction(caseFn)) return caseFn.call(element, element)
|
|
1198
|
+
return !!caseFn
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Evaluate whether a conditional prefix key is active.
|
|
1203
|
+
* Supports $ (global cases), . (truthy), ! (falsy) prefixes.
|
|
1204
|
+
*/
|
|
1205
|
+
const evaluateCondition = (prefix, caseKey, element) => {
|
|
1206
|
+
if (prefix === '$') {
|
|
1207
|
+
let result = resolveCase(caseKey, element)
|
|
1208
|
+
if (result === undefined) result = !!element.props?.[caseKey]
|
|
1209
|
+
return result
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// . and ! prefixes: check props/state/element first, then context.cases
|
|
1213
|
+
let isTruthy = element.props[caseKey] === true || element.state[caseKey] || element[caseKey]
|
|
1214
|
+
if (!isTruthy) {
|
|
1215
|
+
const caseResult = resolveCase(caseKey, element)
|
|
1216
|
+
if (caseResult !== undefined) isTruthy = caseResult
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
return prefix === '.' ? !!isTruthy : !isTruthy
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const CONDITIONAL_PREFIXES = new Set(['$', '.', '!'])
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Extract HTML attributes from conditional blocks ($, ., !) in props.
|
|
1226
|
+
* Returns an attr object with functions that re-evaluate on each update.
|
|
1227
|
+
*/
|
|
1228
|
+
export const extractConditionalAttrs = (props, tag, cssProps) => {
|
|
1229
|
+
const result = {}
|
|
1230
|
+
|
|
1231
|
+
const addConditionalAttr = (attrName, attrVal, prefix, caseKey) => {
|
|
1232
|
+
const capturedVal = attrVal
|
|
1233
|
+
result[attrName] = (el) => {
|
|
1234
|
+
if (!evaluateCondition(prefix, caseKey, el)) return undefined
|
|
1235
|
+
return isFunction(capturedVal) ? capturedVal(el) : capturedVal
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
for (const key in props) {
|
|
1240
|
+
const prefix = key.charAt(0)
|
|
1241
|
+
if (!CONDITIONAL_PREFIXES.has(prefix)) continue
|
|
1242
|
+
|
|
1243
|
+
const block = props[key]
|
|
1244
|
+
if (!block || typeof block !== 'object') continue
|
|
1245
|
+
|
|
1246
|
+
const caseKey = key.slice(1)
|
|
1247
|
+
|
|
1248
|
+
for (const attrKey in block) {
|
|
1249
|
+
if (cssProps && attrKey in cssProps) continue
|
|
1250
|
+
|
|
1251
|
+
// aria: { label: 'foo' } inside conditional block
|
|
1252
|
+
if (attrKey === 'aria' && block[attrKey] && typeof block[attrKey] === 'object') {
|
|
1253
|
+
for (const ariaKey in block[attrKey]) {
|
|
1254
|
+
addConditionalAttr('aria-' + ariaKey, block[attrKey][ariaKey], prefix, caseKey)
|
|
1255
|
+
}
|
|
1256
|
+
continue
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// data: { testId: 'foo' } inside conditional block
|
|
1260
|
+
if (attrKey === 'data' && block[attrKey] && typeof block[attrKey] === 'object') {
|
|
1261
|
+
for (const dataKey in block[attrKey]) {
|
|
1262
|
+
const kebab = dataKey.replace(/([A-Z])/g, (m) => '-' + m.toLowerCase())
|
|
1263
|
+
addConditionalAttr('data-' + kebab, block[attrKey][dataKey], prefix, caseKey)
|
|
1264
|
+
}
|
|
1265
|
+
continue
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
const isAttribute = checkAttributeByTagName(tag, attrKey)
|
|
1269
|
+
const isEvent = checkEventFunctions(attrKey)
|
|
1270
|
+
if (!isAttribute && !isEvent) continue
|
|
1271
|
+
|
|
1272
|
+
const attrName = camelToAttr(attrKey) || attrKey
|
|
1273
|
+
addConditionalAttr(attrName, block[attrKey], prefix, caseKey)
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
return result
|
|
1278
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "attrs-in-props",
|
|
3
3
|
"description": "Utilize props as attributes",
|
|
4
4
|
"author": "symbo.ls",
|
|
5
|
-
"version": "3.8.
|
|
5
|
+
"version": "3.8.1",
|
|
6
6
|
"repository": "https://github.com/symbo-ls/smbls",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "./dist/esm/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"main": "./dist/cjs/index.js",
|
|
12
12
|
"gitHead": "9fc1b79b41cdc725ca6b24aec64920a599634681",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@domql/utils": "^3.8.
|
|
14
|
+
"@domql/utils": "^3.8.1"
|
|
15
15
|
},
|
|
16
16
|
"source": "index.js",
|
|
17
17
|
"browser": "./dist/esm/index.js",
|