words
Version:
A collection of React hooks for toggles with dynamic noun and verb APIs
226 lines (225 loc) • 8.19 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useWords = exports.useVerbs = exports.useNoun = exports.createNounFromState = exports.NounSetter = exports.nounState = exports.negativeVerbs = exports.positiveVerbs = exports.toggleVerbs = void 0;
var react_1 = require("react");
// toggle verb pairs
exports.toggleVerbs = {
open: 'close',
show: 'hide',
turnOn: 'turnOff',
check: 'uncheck',
enable: 'disable',
expand: 'collapse',
activate: 'deactivate',
start: 'end',
connect: 'disconnect',
focus: 'blur',
mount: 'unmount',
reveal: 'conceal',
display: 'dismiss',
lock: 'unlock',
subscribe: 'unsubscribe'
};
exports.positiveVerbs = Object.keys(exports.toggleVerbs);
exports.negativeVerbs = Object.values(exports.toggleVerbs);
// noun state mapping object. For any key that maps to true,
// the getter will return getActive(), and for false it returns !getActive()
exports.nounState = {
isActive: true,
isOpen: true,
isClosed: false,
isShown: true,
isHidden: false,
isVisible: true,
isOn: true,
isOff: false,
isChecked: true,
isEnabled: true,
isDisabled: false,
isExpanded: true,
isCollapsed: false,
isActivated: true,
isDeactivated: false,
hasStarted: true,
hasEnded: false,
isConnected: true,
isDisconnected: false,
isFocused: true,
isBlurred: false,
isMounted: true,
isRevealed: true,
isConcealed: false,
isLocked: true,
isUnlocked: false,
isSubscribed: true
};
exports.NounSetter = Symbol.for('NounSetter');
function createNounFromState(name, getActive, setActive) {
return new Proxy({}, {
get: function (_, prop) {
if (typeof prop === 'symbol') {
if (prop === exports.NounSetter)
return setActive;
return undefined;
}
if (typeof prop === 'string') {
if (prop === 'name')
return name;
if (prop in exports.nounState) {
return exports.nounState[prop] ? getActive() : !getActive();
}
}
return undefined;
}
});
}
exports.createNounFromState = createNounFromState;
function useNoun(initial) {
var _a = (0, react_1.useState)(initial), active = _a[0], setActive = _a[1];
var activeRef = (0, react_1.useRef)(active);
(0, react_1.useLayoutEffect)(function () {
activeRef.current = active;
}, [active]);
var name = (0, react_1.useMemo)(function () { return Math.random().toString(36).substr(2, 6); }, []);
var nounRef = (0, react_1.useRef)();
if (!nounRef.current) {
nounRef.current = createNounFromState(name, function () { return activeRef.current; }, function (val) {
// Update the ref immediately so that getActive returns the new value
activeRef.current = val;
setActive(val);
});
}
return nounRef.current;
}
exports.useNoun = useNoun;
function useVerbs() {
return (0, react_1.useMemo)(function () {
var v = {};
var _loop_1 = function (positive) {
var negative = exports.toggleVerbs[positive];
v[positive] = function (noun) {
var setter = noun[exports.NounSetter];
if (typeof setter === 'function') {
setter(true);
}
else {
console.warn("No setter on noun ".concat(noun.name, " for ").concat(positive));
}
};
v[negative] = function (noun) {
console.log("useVerbs: ".concat(negative, " called on noun ").concat(noun.name));
var setter = noun[exports.NounSetter];
if (typeof setter === 'function') {
setter(false);
}
else {
console.warn("No setter on noun ".concat(noun.name, " for ").concat(negative));
}
};
};
for (var _i = 0, positiveVerbs_1 = exports.positiveVerbs; _i < positiveVerbs_1.length; _i++) {
var positive = positiveVerbs_1[_i];
_loop_1(positive);
}
v.toggle = function (noun) {
var setter = noun[exports.NounSetter];
if (typeof setter === 'function') {
setter(!noun.isActive);
}
else {
console.warn("No setter on noun ".concat(noun.name, " for toggle"));
}
};
return v;
}, []);
}
exports.useVerbs = useVerbs;
function useWords() {
var initialValues = [];
for (var _i = 0; _i < arguments.length; _i++) {
initialValues[_i] = arguments[_i];
}
var rerender = (0, react_1.useReducer)(function () { return ({}); }, 0)[1];
var states = (0, react_1.useRef)({});
var nounCache = (0, react_1.useRef)({});
var index = (0, react_1.useRef)(0);
// Updated verbs: try to use the noun's own setter if available.
var verbs = (0, react_1.useMemo)(function () {
var v = {};
for (var _i = 0, positiveVerbs_2 = exports.positiveVerbs; _i < positiveVerbs_2.length; _i++) {
var positive = positiveVerbs_2[_i];
var negative = exports.toggleVerbs[positive];
v[positive] = function (noun) {
var setter = noun[exports.NounSetter];
if (typeof setter === 'function') {
setter(true);
}
else {
states.current[noun.name] = true;
rerender();
}
};
v[negative] = function (noun) {
var setter = noun[exports.NounSetter];
if (typeof setter === 'function') {
setter(false);
}
else {
states.current[noun.name] = false;
rerender();
}
};
}
v.toggle = function (noun) {
var setter = noun[exports.NounSetter];
if (typeof setter === 'function') {
setter(!noun.isActive);
}
else {
states.current[noun.name] = !states.current[noun.name];
rerender();
}
};
return v;
}, [rerender]);
var nouns = (0, react_1.useMemo)(function () {
return new Proxy({}, {
get: function (_, prop) {
var _a;
if (typeof prop !== 'string')
return undefined;
if (prop in verbs) {
if (process.env.NODE_ENV !== 'production') {
throw new Error("Invalid noun name \"".concat(prop, "\": noun names must not conflict with verb names"));
}
else {
console.error("Invalid noun name \"".concat(prop, "\": noun names must not conflict with verb names"));
return undefined;
}
}
if (nounCache.current[prop])
return nounCache.current[prop];
var initial = (_a = initialValues[index.current]) !== null && _a !== void 0 ? _a : false;
states.current[prop] = initial;
var noun = createNounFromState(prop, function () { return states.current[prop]; }, function (val) {
states.current[prop] = val;
rerender();
});
nounCache.current[prop] = noun;
index.current++;
return noun;
},
ownKeys: function () {
return Object.keys(states.current);
},
getOwnPropertyDescriptor: function (_, prop) {
if (prop in states.current)
return { enumerable: true, configurable: true };
return undefined;
}
});
}, [initialValues, rerender]);
return [nouns, verbs];
}
exports.useWords = useWords;
exports.default = useWords;