UNPKG

@activecollab/components

Version:

ActiveCollab Components

238 lines (221 loc) 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.snap = exports.measurePercent = exports.makeStart = exports.keyboardResize = exports.isResizeKey = exports.effectiveBounds = exports.clamp = exports.applyResize = exports.SHIFT_STEP_MULTIPLIER = exports.RESIZE_KEYS = exports.DEFAULT_KEYBOARD_STEP = void 0; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /** * StackedCard resize policy — pure math. * * The card reports intent; the HOST applies a resize policy (spec §7). This * module is that policy, packaged as pure functions so the SAME code path * serves the pointer drag and the keyboard resize. Nothing here touches the * DOM, React or the card's content: it takes a start size, a delta and a * policy, and returns a clamped size. `useStackedCardResize` wires events to * it; the card's intrinsic content minimum is measured by the host and passed * in as `contentMin`, so this module knows nothing about heroes or footers. * * The policy has four parts: * - bounds — optional min/max per axis; unset means unbounded. * - proportional — lock the aspect ratio captured at the start of the gesture; * the scale is clamped by the BINDING axis so the ratio never * distorts at a bound. * - step — optional grid snap; proportion wins over the grid. * - content min — the effective min is max(policyMin, contentMin) per axis, * so a card can never be crushed below what its content needs. */ /** The card's intrinsic minimum, measured from its real content. */ /** The size captured at the start of a gesture (pointer down or a key press). */ /** * `width` locks the vertical axis: an auto-height card takes a width and lets * its rows set the height, so the gesture's vertical component is ignored. */ /** Which input drove a resize event. Hosts log undo entries per source. */ /** Keyboard resize nudges by the policy step, or this when no step is set. */ var DEFAULT_KEYBOARD_STEP = exports.DEFAULT_KEYBOARD_STEP = 8; /** Shift + arrow resizes by a larger increment (the familiar coarse nudge). */ var SHIFT_STEP_MULTIPLIER = exports.SHIFT_STEP_MULTIPLIER = 4; /** Keys the separator handles; everything else falls through to the browser. */ var RESIZE_KEYS = exports.RESIZE_KEYS = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"]; var isResizeKey = exports.isResizeKey = function isResizeKey(key) { return RESIZE_KEYS.includes(key); }; var clamp = exports.clamp = function clamp(v, lo, hi) { return Math.min(hi !== null && hi !== void 0 ? hi : Number.POSITIVE_INFINITY, Math.max(lo, v)); }; var snap = exports.snap = function snap(v, step) { return step && step > 0 ? Math.round(v / step) * step : v; }; var makeStart = exports.makeStart = function makeStart(size, contentMin) { return { w: size.w, h: size.h, contentMin }; }; /** * The effective bounds a size is clamped to: the policy min floored by the * content min, and the policy max (unbounded when unset). Independent of any * live gesture, so a host can also use it for the aria value and Home/End. */ var effectiveBounds = exports.effectiveBounds = function effectiveBounds(policy, contentMin) { var _policy$minW, _policy$minH, _policy$maxW, _policy$maxH; return { wMin: Math.max((_policy$minW = policy.minW) !== null && _policy$minW !== void 0 ? _policy$minW : 0, contentMin.w), hMin: Math.max((_policy$minH = policy.minH) !== null && _policy$minH !== void 0 ? _policy$minH : 0, contentMin.h), wMax: (_policy$maxW = policy.maxW) !== null && _policy$maxW !== void 0 ? _policy$maxW : Number.POSITIVE_INFINITY, hMax: (_policy$maxH = policy.maxH) !== null && _policy$maxH !== void 0 ? _policy$maxH : Number.POSITIVE_INFINITY }; }; /** * Whether an axis has a floor anyone asked for. `effectiveBounds` reports 0 for * an unset minimum — correct as a clamp, but not something Home should jump to: * an axis nobody gave a minimum must not collapse to nothing. */ var hasFloor = function hasFloor(policyMin, contentMin) { return policyMin !== undefined || contentMin > 0; }; /** * Apply a delta to the start size under the policy — the single path for both * inputs. * * `constrain` is the transient Shift-to-constrain lock: a free-form policy * behaves proportionally for that one gesture; a proportional policy is already * locked and ignores it. `axis: "width"` drops the gesture's vertical component; * the height then holds still in free-form mode, and still follows the ratio in * proportional mode (that is what proportional means). */ var applyResize = exports.applyResize = function applyResize(start, delta, policy) { var opts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var _effectiveBounds = effectiveBounds(policy, start.contentMin), wMin = _effectiveBounds.wMin, hMin = _effectiveBounds.hMin, wMax = _effectiveBounds.wMax, hMax = _effectiveBounds.hMax; var widthOnly = opts.axis === "width"; var dx = delta.dx; var dy = widthOnly ? 0 : delta.dy; // A ratio needs two non-zero sides to be a ratio at all; a width-only card // parks a placeholder height, so fall back to free-form rather than dividing // by zero. var proportional = (policy.proportional || opts.constrain === true) && start.w > 0 && start.h > 0; var w; var h; if (proportional) { // one scale drives both axes; the corner's dominant direction wins, so a // vertical drag is as effective as a horizontal one var sW = (start.w + dx) / start.w; var sH = (start.h + dy) / start.h; var scale = Math.abs(sW - 1) >= Math.abs(sH - 1) ? sW : sH; // step: proportion wins over the grid. Snap through the SCALE (driven off // the width edge) so both axes move together and the ratio survives; the // grid yields whenever honouring it would cost the ratio. if (policy.step) { scale = snap(start.w * scale, policy.step) / start.w; } // Then clamp the scale so both axes stay in bounds (the binding constraint): // the ratio can never break at a bound — whichever edge is limiting stops // both. The clamp comes last on purpose: a bound is a hard stop, so the card // sits exactly on it rather than at the nearest grid line inside it. var scaleMin = Math.max(wMin / start.w, hMin / start.h); var scaleMax = Math.min(wMax / start.w, hMax / start.h); scale = clamp(scale, scaleMin, Math.max(scaleMin, scaleMax)); w = start.w * scale; h = start.h * scale; } else { w = clamp(snap(start.w + dx, policy.step), wMin, wMax); h = widthOnly ? start.h : clamp(snap(start.h + dy, policy.step), hMin, hMax); } return { w: Math.round(w), h: Math.round(h) }; }; /** * Translate a resize key into the next size, reusing `applyResize` so the * keyboard clamps exactly like the pointer. Arrows nudge by the step (Shift ×4); * Home/End jump to the min/max of the allowed range, leaving any axis without * that bound where it is. Returns null for a key the handle does not own, and * for a vertical key on a width-only card. */ var keyboardResize = exports.keyboardResize = function keyboardResize(key, size, policy, contentMin) { var opts = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; var widthOnly = opts.axis === "width"; if (widthOnly && (key === "ArrowUp" || key === "ArrowDown")) return null; var base = policy.step && policy.step > 0 ? policy.step : DEFAULT_KEYBOARD_STEP; var step = base * (opts.shiftKey ? SHIFT_STEP_MULTIPLIER : 1); var start = makeStart(size, contentMin); var b = effectiveBounds(policy, contentMin); var apply = function apply(delta) { return applyResize(start, delta, policy, { axis: opts.axis }); }; /** * Home and End are absolute jumps to a bound, not nudges, so they ignore the * grid: landing 4px off the minimum because that is where the nearest grid * line fell would mean the keyboard could never reach the bound the pointer * stops at. */ var jump = function jump(delta) { return applyResize(start, delta, _objectSpread(_objectSpread({}, policy), {}, { step: 0 }), { axis: opts.axis }); }; switch (key) { case "ArrowRight": return apply({ dx: step, dy: 0 }); case "ArrowLeft": return apply({ dx: -step, dy: 0 }); case "ArrowDown": return apply({ dx: 0, dy: step }); case "ArrowUp": return apply({ dx: 0, dy: -step }); case "Home": // jump to the min corner; an axis with no floor of its own stays put return jump({ dx: hasFloor(policy.minW, contentMin.w) ? b.wMin - size.w : 0, dy: !widthOnly && hasFloor(policy.minH, contentMin.h) ? b.hMin - size.h : 0 }); case "End": // jump to the max corner; an unbounded axis simply doesn't move return jump({ dx: Number.isFinite(b.wMax) ? b.wMax - size.w : 0, dy: !widthOnly && Number.isFinite(b.hMax) ? b.hMax - size.h : 0 }); default: return null; } }; /** * The handle's `aria-valuenow`: the current size as a percent (0–100) of its * allowed range. The corner resizes both axes, but the value must be a single * number, so it reports the WIDTH axis — the one axis the fixed-box and the * width-only cards share. Returns 0 when the range isn't finite (no max bound). */ var measurePercent = exports.measurePercent = function measurePercent(size, policy, contentMin) { var _effectiveBounds2 = effectiveBounds(policy, contentMin), wMin = _effectiveBounds2.wMin, wMax = _effectiveBounds2.wMax; if (!Number.isFinite(wMax) || wMax <= wMin) return 0; return Math.round(clamp((size.w - wMin) / (wMax - wMin) * 100, 0, 100)); }; //# sourceMappingURL=resizePolicy.js.map