ng-hub-ui-utils 22.18.0 → 22.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -539,6 +539,11 @@ Also exported: `resolveGridEdge`, `isWithinGridRange`, `gridRangeCells`, `gridRa
539
539
  `HubGridCoords`, `HubGridBounds`, `HubGridRange`, `HubGridSpan`, `HubGridSpanMap`, `HubGridWrap`,
540
540
  `HubGridEdge`, `HubGridIntent`, `HubGridWindow` and `HubGridTrackSizes`.
541
541
 
542
+ Also exported for a selection of several rectangles: `isWithinGridSelection`, `addGridRange`,
543
+ `gridSelectionCells`, `gridSelectionSize`, `gridSelectionBounds`, `clampGridSelection` and
544
+ `gridSelectionTable` — the last of which decides whether a disjoint selection can be copied at all
545
+ (it can when the rectangles line up, and returns `null` when there is no honest table to write).
546
+
542
547
  ## 🚀 Installation
543
548
 
544
549
  ```bash
@@ -2183,6 +2183,127 @@ function between(value, length) {
2183
2183
  return Math.max(0, Math.min(length - 1, Math.trunc(value)));
2184
2184
  }
2185
2185
 
2186
+ /**
2187
+ * A selection made of several rectangles, which is what a grid has the moment somebody holds
2188
+ * `Ctrl` and clicks a second block.
2189
+ *
2190
+ * Kept as a list of rectangles rather than as a set of cells, for the same reason one rectangle is
2191
+ * kept as two corners: a reader picking four blocks of a thousand rows has picked four rectangles,
2192
+ * and turning that into four thousand coordinates to answer "is this cell selected" is how a sheet
2193
+ * starts feeling slow.
2194
+ *
2195
+ * The one rule worth knowing before reading the rest: **a disjoint selection cannot always be
2196
+ * copied.** Two blocks side by side make a table; two blocks that overlap nothing and line up with
2197
+ * nothing make no table at all, and a spreadsheet says so rather than inventing one. That is what
2198
+ * {@link gridSelectionTable} decides, and it is the same rule Excel applies when it refuses with
2199
+ * "that command cannot be used on multiple selections".
2200
+ */
2201
+ /** Whether a cell falls inside any rectangle of the selection. */
2202
+ function isWithinGridSelection(coords, ranges) {
2203
+ return ranges.some((range) => isWithinGridRange(coords, range));
2204
+ }
2205
+ /** Whether one rectangle holds another whole. */
2206
+ function contains(outer, inner) {
2207
+ return inner.top >= outer.top && inner.bottom <= outer.bottom && inner.left >= outer.left && inner.right <= outer.right;
2208
+ }
2209
+ /**
2210
+ * Adds a rectangle to a selection.
2211
+ *
2212
+ * Rectangles already swallowed by the newcomer are dropped, and a newcomer already inside one is
2213
+ * not added: without that, clicking about with `Ctrl` held quietly grows a list of rectangles that
2214
+ * all say the same thing, and every later answer — the cell count, the clipboard, the clearing —
2215
+ * has to de-duplicate what should never have been stored twice. Overlaps that are *partial* are
2216
+ * kept as they are, because they are two decisions the reader made and neither contains the other.
2217
+ */
2218
+ function addGridRange(ranges, range) {
2219
+ if (ranges.some((existing) => contains(existing, range))) {
2220
+ return [...ranges];
2221
+ }
2222
+ return [...ranges.filter((existing) => !contains(range, existing)), range];
2223
+ }
2224
+ /** Every rectangle trimmed to the grid, with the ones that no longer land anywhere dropped. */
2225
+ function clampGridSelection(ranges, bounds) {
2226
+ return ranges.map((range) => clampGridRange(range, bounds)).filter((range) => !!range);
2227
+ }
2228
+ /**
2229
+ * Every cell of the selection, each one once, row by row and left to right.
2230
+ *
2231
+ * The order matters as much as it does for a single rectangle: it is the order a paste reads and the
2232
+ * order a clearing reports, so a host can walk it without sorting anything.
2233
+ */
2234
+ function gridSelectionCells(ranges) {
2235
+ const seen = new Set();
2236
+ const cells = [];
2237
+ for (const range of ranges) {
2238
+ for (const cell of gridRangeCells(range)) {
2239
+ const key = `${cell.row}\t${cell.col}`;
2240
+ if (!seen.has(key)) {
2241
+ seen.add(key);
2242
+ cells.push(cell);
2243
+ }
2244
+ }
2245
+ }
2246
+ return cells.sort((a, b) => a.row - b.row || a.col - b.col);
2247
+ }
2248
+ /** How many cells the selection covers, counting a cell in two rectangles once. */
2249
+ function gridSelectionSize(ranges) {
2250
+ return gridSelectionCells(ranges).length;
2251
+ }
2252
+ /** The smallest rectangle holding every rectangle of the selection. */
2253
+ function gridSelectionBounds(ranges) {
2254
+ if (!ranges.length) {
2255
+ return null;
2256
+ }
2257
+ return ranges.reduce((all, range) => ({
2258
+ top: Math.min(all.top, range.top),
2259
+ bottom: Math.max(all.bottom, range.bottom),
2260
+ left: Math.min(all.left, range.left),
2261
+ right: Math.max(all.right, range.right)
2262
+ }));
2263
+ }
2264
+ /**
2265
+ * The rows and columns a copy of this selection would write, or null when it cannot be copied.
2266
+ *
2267
+ * A selection of one rectangle is always copyable. Several are copyable only when they line up: all
2268
+ * of them covering the same columns, so they stack into one table, or all of them covering the same
2269
+ * rows, so they sit side by side. Anything else has no rectangle to write — the blocks would have to
2270
+ * be squashed together and the reader would get a table whose shape they never chose.
2271
+ *
2272
+ * @returns The row indices and the column indices of the table, in the order they go out, or null
2273
+ * when the selection has no honest table.
2274
+ */
2275
+ function gridSelectionTable(ranges) {
2276
+ if (!ranges.length) {
2277
+ return null;
2278
+ }
2279
+ const sequence = (from, to) => Array.from({ length: to - from + 1 }, (_, index) => from + index);
2280
+ if (ranges.length === 1) {
2281
+ return { rows: sequence(ranges[0].top, ranges[0].bottom), cols: sequence(ranges[0].left, ranges[0].right) };
2282
+ }
2283
+ const [first] = ranges;
2284
+ const sameColumns = ranges.every((range) => range.left === first.left && range.right === first.right);
2285
+ if (sameColumns) {
2286
+ const stacked = [...ranges].sort((a, b) => a.top - b.top);
2287
+ return {
2288
+ rows: unique(stacked.flatMap((range) => sequence(range.top, range.bottom))),
2289
+ cols: sequence(first.left, first.right)
2290
+ };
2291
+ }
2292
+ const sameRows = ranges.every((range) => range.top === first.top && range.bottom === first.bottom);
2293
+ if (sameRows) {
2294
+ const beside = [...ranges].sort((a, b) => a.left - b.left);
2295
+ return {
2296
+ rows: sequence(first.top, first.bottom),
2297
+ cols: unique(beside.flatMap((range) => sequence(range.left, range.right)))
2298
+ };
2299
+ }
2300
+ return null;
2301
+ }
2302
+ /** Keeps the first occurrence of each index, so overlapping rectangles do not repeat a row. */
2303
+ function unique(values) {
2304
+ return [...new Set(values)];
2305
+ }
2306
+
2186
2307
  /**
2187
2308
  * Merged blocks, and what the rest of the grid has to know about them.
2188
2309
  *
@@ -4530,5 +4651,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4530
4651
  * Generated bundle index. Do not edit.
4531
4652
  */
4532
4653
 
4533
- export { ContentRef, FOCUSABLE_ELEMENTS_SELECTOR, GetPipe, HUB_DROPDOWN_POSITIONS, HUB_INK_LIGHTNESS_THRESHOLD, HUB_MAX_HUE_SHIFT, HUB_MAX_NEUTRAL_CHROMA, HUB_NAMED_COLORS, HUB_NEUTRAL_ANCHORS, HUB_SEMANTIC_ANCHORS, HUB_TOOLTIP_ADAPTER, HUB_TRANSLATION_CONFIG, HUB_TRANSLATION_PREFIX, HUB_TRANSLATION_SOURCE, HubDragDropService, HubGetPipe, HubIsObjectPipe, HubIsObservablePipe, HubIsStringPipe, HubOverflowTooltipDirective, HubTooltipController, HubTooltipDirective, HubTranslatePipe, HubTranslationService, HubUnwrapAsyncPipe, IsObjectPipe, IsObservablePipe, IsStringPipe, OverlayPosition, OverlayRef, OverlayService, PopupService, ScrollBar, TranslatePipe, UcfirstPipe, UnwrapAsyncPipe, anchorOf, buildSpanMap, clamp$1 as clamp, clampGridRange, clampToSrgbGamut, closest, compositeOver, computeTargetIndex, containsNode, contrastAPCA, contrastRatio, copyArrayItem, coversRange, createNativeDragImage, createPointerDragSession, debouncedSignal, equals, generateUniqueId, getActiveElement, getFocusableBoundaryElements, getValue, getValueInRange, gridCellTabIndex, gridRangeBetween, gridRangeCells, gridRangeSize, gridWindow, growWindowToSpans, harmoniseSemantics, hubAnchorToViewport, hubClampToViewport, hubCompleteTransition, hubFitsInViewport, hubFocusTrap, hubOppositeSide, hubRunTransition, hubToAnchorSide, hubToPhysicalSide, hubTooltipAdapter, hubViewportOf, interpolateString, isCovered, isDefined, isInSrgbGamut, isInteger, isNumber, isObject, isPromise, isString, isValidColor, isWithinGridRange, maxSrgbChroma, mergeDeep, moveGridFocus, moveItemInArray, oklchToRgb, padNumber, parseColor, provideHubTooltip, provideHubTranslation, provideHubTranslationAdapter, readableOn, reflow, regExpEscape, registerOverlayKeydown, relativeLuminance, removeAccents, resolveDropPosition, resolveGridEdge, resolveGridIntent, resolveHubAccent, rgbToOklch, runInZone, spanAt, tintNeutrals, toAbsoluteIndex, toHex, toInteger, toRgb, toString, transferArrayItem };
4654
+ export { ContentRef, FOCUSABLE_ELEMENTS_SELECTOR, GetPipe, HUB_DROPDOWN_POSITIONS, HUB_INK_LIGHTNESS_THRESHOLD, HUB_MAX_HUE_SHIFT, HUB_MAX_NEUTRAL_CHROMA, HUB_NAMED_COLORS, HUB_NEUTRAL_ANCHORS, HUB_SEMANTIC_ANCHORS, HUB_TOOLTIP_ADAPTER, HUB_TRANSLATION_CONFIG, HUB_TRANSLATION_PREFIX, HUB_TRANSLATION_SOURCE, HubDragDropService, HubGetPipe, HubIsObjectPipe, HubIsObservablePipe, HubIsStringPipe, HubOverflowTooltipDirective, HubTooltipController, HubTooltipDirective, HubTranslatePipe, HubTranslationService, HubUnwrapAsyncPipe, IsObjectPipe, IsObservablePipe, IsStringPipe, OverlayPosition, OverlayRef, OverlayService, PopupService, ScrollBar, TranslatePipe, UcfirstPipe, UnwrapAsyncPipe, addGridRange, anchorOf, buildSpanMap, clamp$1 as clamp, clampGridRange, clampGridSelection, clampToSrgbGamut, closest, compositeOver, computeTargetIndex, containsNode, contrastAPCA, contrastRatio, copyArrayItem, coversRange, createNativeDragImage, createPointerDragSession, debouncedSignal, equals, generateUniqueId, getActiveElement, getFocusableBoundaryElements, getValue, getValueInRange, gridCellTabIndex, gridRangeBetween, gridRangeCells, gridRangeSize, gridSelectionBounds, gridSelectionCells, gridSelectionSize, gridSelectionTable, gridWindow, growWindowToSpans, harmoniseSemantics, hubAnchorToViewport, hubClampToViewport, hubCompleteTransition, hubFitsInViewport, hubFocusTrap, hubOppositeSide, hubRunTransition, hubToAnchorSide, hubToPhysicalSide, hubTooltipAdapter, hubViewportOf, interpolateString, isCovered, isDefined, isInSrgbGamut, isInteger, isNumber, isObject, isPromise, isString, isValidColor, isWithinGridRange, isWithinGridSelection, maxSrgbChroma, mergeDeep, moveGridFocus, moveItemInArray, oklchToRgb, padNumber, parseColor, provideHubTooltip, provideHubTranslation, provideHubTranslationAdapter, readableOn, reflow, regExpEscape, registerOverlayKeydown, relativeLuminance, removeAccents, resolveDropPosition, resolveGridEdge, resolveGridIntent, resolveHubAccent, rgbToOklch, runInZone, spanAt, tintNeutrals, toAbsoluteIndex, toHex, toInteger, toRgb, toString, transferArrayItem };
4534
4655
  //# sourceMappingURL=ng-hub-ui-utils.mjs.map