UNPKG

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
"use strict"; 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, "\"")); } }); } } };