@activecollab/components
Version:
ActiveCollab Components
238 lines (221 loc) • 11.2 kB
JavaScript
;
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