react-cmp-selector
Version:
A powerful and extensible utility for filtering and selecting React components based on specified attributes and values.
252 lines (251 loc) • 10.6 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SlotUtils = void 0;
exports.getCmpByAttr = getCmpByAttr;
exports.Slot = Slot;
var react_1 = __importStar(require("react"));
/**
* Custom React hook for finding components by attribute with enhanced capabilities
*
* @remarks
* This hook provides advanced component searching with prop merging, debug capabilities,
* and support for complex React trees. It's ideal for component injection patterns.
*
* @template P - Props type of the target component
* @param options - Configuration options for the finder
* @returns Found component(s) or null
*
* @example
* // Find and modify a header component
* const header = getCmpByAttr({
* value: 'header',
* props: { className: 'sticky-header' }
* });
*
* @example
* // Find all matching buttons with combined click handlers
* const buttons = getCmpByAttr({
* attribute: 'data-role',
* value: 'action-button',
* findAll: true,
* functionPropMerge: 'combine'
* });
*/
function getCmpByAttr(_a) {
var children = _a.children, _b = _a.attribute, attribute = _b === void 0 ? 'data-slot' : _b, _c = _a.value, value = _c === void 0 ? '' : _c, _d = _a.props, props = _d === void 0 ? {} : _d, _e = _a.debug, debug = _e === void 0 ? false : _e, _f = _a.findAll, findAll = _f === void 0 ? false : _f, onFound = _a.onFound, _g = _a.functionPropMerge, functionPropMerge = _g === void 0 ? 'combine' : _g;
var propsRef = (0, react_1.useMemo)(function () { return props; }, [props]);
var mergeProps = (0, react_1.useCallback)(function (original, newProps) {
var merged = __assign({}, original);
var _loop_1 = function (key, val) {
var existingProp = merged[key];
if (functionPropMerge === 'combine' &&
typeof existingProp === 'function' &&
typeof val === 'function') {
merged[key] = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
existingProp.apply(void 0, args);
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
val.apply(void 0, args);
};
}
else {
merged[key] = val;
}
};
for (var _i = 0, _a = Object.entries(newProps); _i < _a.length; _i++) {
var _b = _a[_i], key = _b[0], val = _b[1];
_loop_1(key, val);
}
return merged;
}, [functionPropMerge]);
var searchChildren = (0, react_1.useCallback)(function (nodes) {
var matches = [];
var walkTree = function (node) {
if (!(0, react_1.isValidElement)(node))
return;
var element = node;
var elementProps = element.props;
// Check current element
if (elementProps[attribute] === value) {
matches.push(element);
onFound === null || onFound === void 0 ? void 0 : onFound(element);
}
// Recursively search children
if (elementProps.children) {
react_1.default.Children.forEach(elementProps.children, walkTree);
}
};
react_1.default.Children.forEach(nodes, walkTree);
return matches;
}, [attribute, value, onFound]);
var foundComponents = (0, react_1.useMemo)(function () { return searchChildren(children); }, [children, searchChildren]);
(0, react_1.useEffect)(function () {
if (debug && process.env.NODE_ENV !== 'production') {
console.groupCollapsed("[Component Finder] ".concat(attribute, "=\"").concat(value, "\""));
console.log('Search Parameters:', { attribute: attribute, value: value });
console.log('Matches Found:', foundComponents.length);
console.log('Components:', foundComponents);
console.groupEnd();
}
}, [debug, foundComponents, attribute, value]);
return (0, react_1.useMemo)(function () {
if (foundComponents.length === 0) {
if (debug && process.env.NODE_ENV !== 'production') {
console.warn("No components found with ".concat(attribute, "=\"").concat(value, "\".\nAvailable attributes:"), react_1.default.Children.toArray(children)
.filter(react_1.isValidElement)
.flatMap(function (c) { return Object.entries(c.props)
.filter(function (_a) {
var k = _a[0];
return k.startsWith('data-');
})
.map(function (_a) {
var k = _a[0], v = _a[1];
return "".concat(k, ": ").concat(v);
}); }));
}
return null;
}
var processComponent = function (component) {
try {
return (0, react_1.cloneElement)(component, mergeProps(component.props, propsRef));
}
catch (error) {
if (process.env.NODE_ENV !== 'production') {
console.error('Component cloning error:', error);
}
return component;
}
};
var result = findAll
? foundComponents.map(processComponent)
: processComponent(foundComponents[0]);
return result;
}, [foundComponents, propsRef, debug, findAll, attribute, value, mergeProps]);
}
/**
* Declarative component version of getCmpByAttr
*
* @remarks
* Provides a React component interface for the slot finding functionality
* with additional validation and fallback capabilities.
*
* @example
* <Slot name="header" fallback={<DefaultHeader />}>
* {children}
* </Slot>
*/
function Slot(_a) {
var children = _a.children, name = _a.name, _b = _a.attribute, attribute = _b === void 0 ? 'data-slot' : _b, _c = _a.props, props = _c === void 0 ? {} : _c, _d = _a.debug, debug = _d === void 0 ? false : _d, _e = _a.fallback, fallback = _e === void 0 ? null : _e, _f = _a.functionPropMerge, functionPropMerge = _f === void 0 ? 'combine' : _f;
var component = getCmpByAttr({
children: children,
attribute: attribute,
value: name,
props: props,
debug: debug,
functionPropMerge: functionPropMerge
});
return component || fallback;
}
/**
* Utility functions for working with slots
*/
exports.SlotUtils = {
/**
* Creates a slot marker component for type-safe slot declaration
*
* @param name - Slot identifier
* @param attribute - Attribute name to use (default: 'data-slot')
* @returns Slot marker component
*
* @example
* const HeaderSlot = SlotUtils.createMarker('header');
* <HeaderSlot>...</HeaderSlot>
*/
createMarker: function (name, attribute) {
if (attribute === void 0) { attribute = 'data-slot'; }
return function (_a) {
var _b;
var children = _a.children, props = __rest(_a, ["children"]);
return react_1.default.createElement('div', __assign(__assign({}, props), (_b = { style: __assign({ display: 'contents' }, (typeof props.style === 'object' && props.style !== null ? props.style : {})) }, _b[attribute] = name, _b)), children);
};
},
/**
* Validates required slots in development environment
*
* @param children - Component children to validate
* @param requiredSlots - Array of required slot names
* @param attribute - Attribute name to check (default: 'data-slot')
*/
validate: function (children, requiredSlots, attribute) {
if (attribute === void 0) { attribute = 'data-slot'; }
if (process.env.NODE_ENV === 'development') {
var presentSlots_1 = react_1.default.Children.toArray(children)
.filter(react_1.isValidElement)
.map(function (child) { return child.props[attribute]; })
.filter(Boolean);
requiredSlots.forEach(function (slot) {
if (!presentSlots_1.includes(slot)) {
console.warn("Missing required slot: \"".concat(slot, "\""));
}
});
}
}
};
;