v-fit-children 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ozgur Seyidoglu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,268 @@
1
+ # v-fit-children
2
+
3
+ ## Vue directive to hide overflowing children automagically ‼️
4
+
5
+ > **Note:** Watch the [usage example video](https://github.com/ozJSey/v-fit-children-resources/blob/main/Screen%20Recording%202026-02-08%20at%2019.09.25.mov) to see the directive in action (temporary link).
6
+
7
+ A Vue 3 directive that automatically hides child elements that don't fit within a container's width. Ideal for chips, badges, tags, or any inline elements in a tight space.
8
+
9
+ ## Features
10
+
11
+ - Hides children that overflow the container width
12
+ - Emits a custom event with hidden children count and references (for "+N more" indicators)
13
+ - Supports `gap` / `column-gap` in parent container
14
+ - Accounts for margins, padding, and borders on both container and children
15
+ - Option to hide largest items first (`sortBySize`) to maximize visible count
16
+ - Pin specific children so they are never hidden (`keepVisibleEl` or `data-v-fit-keep`)
17
+ - Multi-row support via `rowCount`
18
+ - Responds to container resizes via `ResizeObserver`
19
+ - Monitors individual child size changes via `ResizeObserver`
20
+ - Detects child additions/removals via `MutationObserver`
21
+ - Caches child widths — only re-measures when children change
22
+ - Batches recalculations with `requestAnimationFrame` for performance
23
+ - Written in TypeScript — ships with full type declarations
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install v-fit-children
29
+ ```
30
+
31
+ Vue 3 is a peer dependency — it won't be bundled.
32
+
33
+ ## Quick start
34
+
35
+ ```vue
36
+ <script setup lang="ts">
37
+ import { ref } from "vue";
38
+ import { vFitChildren } from "v-fit-children";
39
+
40
+ const containerRef = ref<HTMLElement>();
41
+ const hiddenCount = ref(0);
42
+
43
+ function onUpdate(e: CustomEvent) {
44
+ hiddenCount.value = e.detail.hiddenChildrenCount;
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <div ref="containerRef">
50
+ <div
51
+ v-fit-children="{
52
+ widthRestrictingContainer: containerRef, // Optional: defaults to this element
53
+ offsetNeededInPx: 50, // Optional: defaults to 50
54
+ }"
55
+ @fit-children-updated="onUpdate"
56
+ >
57
+ <span v-for="tag in tags" :key="tag">{{ tag }}</span>
58
+ </div>
59
+ <span v-if="hiddenCount">+{{ hiddenCount }} more</span>
60
+ </div>
61
+ </template>
62
+ ```
63
+
64
+ The directive element and the width-restricting container can be the same element or different elements. When they differ, the directive element's own margin, border, and padding are subtracted from the available space.
65
+
66
+ ## Options
67
+
68
+ All options are passed as the directive value:
69
+
70
+ ```vue
71
+ <div v-fit-children="{ widthRestrictingContainer: containerRef, offsetNeededInPx: 80 }">
72
+ ```
73
+
74
+ | Option | Type | Default | Description |
75
+ |---|---|---|---|
76
+ | `widthRestrictingContainer` | `HTMLElement` | Directive Element | The element whose width constrains the children. Defaults to the element the directive is on. |
77
+ | `offsetNeededInPx` | `number` | `50` | Reserved space in px (e.g. for a "+N more" badge). Set to `0` if you don't need reserved space. |
78
+ | `gap` | `number` | Computed `gap` | Manually specify the gap between items in pixels. Useful if `gap` CSS is not used (e.g. inline-block margins). |
79
+ | `sortBySize` | `boolean` | `false` | If `true`, hides larger items first to maximize the number of visible items. If `false`, hides items from the end. |
80
+ | `keepVisibleEl` | `HTMLElement` | — | An element (or descendant of a child) that should never be hidden. Useful for inputs or interactive elements. |
81
+ | `rowCount` | `number` | `1` | Number of rows to fill before hiding. Offset is only reserved on the last row. If >1, sets `flex-wrap: wrap`. |
82
+
83
+ Options are reactive — changing them via the directive value triggers a recalculation.
84
+
85
+ ## TypeScript
86
+
87
+ The package ships with full type declarations. Exported types:
88
+
89
+ ```ts
90
+ import { vFitChildren } from "v-fit-children";
91
+ import type { FitChildrenOptions, FitChildrenEventDetail } from "v-fit-children";
92
+ ```
93
+
94
+ ### `FitChildrenOptions`
95
+
96
+ ```ts
97
+ interface FitChildrenOptions {
98
+ gap?: number;
99
+ keepVisibleEl?: HTMLElement;
100
+ offsetNeededInPx?: number;
101
+ rowCount?: number;
102
+ sortBySize?: boolean;
103
+ widthRestrictingContainer?: HTMLElement;
104
+ }
105
+ ```
106
+
107
+ ### `FitChildrenEventDetail`
108
+
109
+ ```ts
110
+ type FitChildrenEventDetail = {
111
+ hiddenChildren: HTMLElement[];
112
+ hiddenChildrenCount: number;
113
+ isOverflowing: boolean;
114
+ };
115
+ ```
116
+
117
+ ### Typing the event handler
118
+
119
+ Vue's `@fit-children-updated` handler receives a `CustomEvent`. You can type it like this:
120
+
121
+ ```ts
122
+ function onUpdate(e: CustomEvent<FitChildrenEventDetail>) {
123
+ console.log(e.detail.hiddenChildrenCount);
124
+ console.log(e.detail.hiddenChildren); // HTMLElement[]
125
+ console.log(e.detail.isOverflowing); // boolean
126
+ }
127
+ ```
128
+
129
+ ## Event
130
+
131
+ The directive dispatches a `fit-children-updated` custom event on the directive's element whenever visibility is recalculated.
132
+
133
+ ```vue
134
+ <div
135
+ v-fit-children="{ widthRestrictingContainer: containerRef }"
136
+ @fit-children-updated="onUpdate"
137
+ >
138
+ ```
139
+
140
+ The event's `detail` contains:
141
+
142
+ | Property | Type | Description |
143
+ |---|---|---|
144
+ | `hiddenChildrenCount` | `number` | Number of children that were hidden |
145
+ | `hiddenChildren` | `HTMLElement[]` | Direct references to the hidden DOM elements |
146
+ | `isOverflowing` | `boolean` | `true` if any children were hidden, `false` if all fit |
147
+
148
+ When all children fit (including the offset), `isOverflowing` is `false` and no offset space is reserved — the "+N" badge is unnecessary.
149
+
150
+ ## Keeping elements visible
151
+
152
+ You can prevent specific children from being hidden. This is useful for inputs, buttons, or any interactive element that should always remain accessible.
153
+
154
+ **Option A — via directive value (`keepVisibleEl`):**
155
+
156
+ Pass a ref to the element (or a descendant of a child) that should stay visible:
157
+
158
+ ```vue
159
+ <script setup lang="ts">
160
+ import { ref } from "vue";
161
+ import { vFitChildren } from "v-fit-children";
162
+
163
+ const containerRef = ref<HTMLElement>();
164
+ const inputRef = ref<HTMLElement>();
165
+ </script>
166
+
167
+ <template>
168
+ <div ref="containerRef">
169
+ <div v-fit-children="{ widthRestrictingContainer: containerRef, keepVisibleEl: inputRef }">
170
+ <span v-for="tag in tags" :key="tag">{{ tag }}</span>
171
+ <div class="input-wrapper">
172
+ <input ref="inputRef" />
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </template>
177
+ ```
178
+
179
+ The directive walks up from `keepVisibleEl` to find the matching immediate child. So if `inputRef` points to a nested `<input>`, the parent child that contains it stays visible.
180
+
181
+ **Option B — via data attribute (`data-v-fit-keep`):**
182
+
183
+ Add the `data-v-fit-keep` attribute directly on the child element — no ref needed:
184
+
185
+ ```vue
186
+ <div v-fit-children="{ widthRestrictingContainer: containerRef }">
187
+ <span v-for="tag in tags" :key="tag">{{ tag }}</span>
188
+ <div data-v-fit-keep>
189
+ <input />
190
+ </div>
191
+ </div>
192
+ ```
193
+
194
+ Both methods can be used together. If a kept element is wider than the available space, it stays visible anyway — better to overflow than to hide an input the user is typing in.
195
+
196
+ ## Sorting by size
197
+
198
+ By default, children are hidden from the end (last child first). Enable `sortBySize` to hide the largest children first, maximizing the number of visible items:
199
+
200
+ ```vue
201
+ <div v-fit-children="{ widthRestrictingContainer: containerRef, sortBySize: true }">
202
+ <span style="width: 200px">Wide tag</span>
203
+ <span style="width: 60px">Small</span>
204
+ <span style="width: 60px">Small</span>
205
+ </div>
206
+ ```
207
+
208
+ With `sortBySize: true`, the "Wide tag" is hidden first, leaving both small tags visible.
209
+
210
+ ## Multi-row layout
211
+
212
+ Use `rowCount` to allow children to fill multiple rows before hiding:
213
+
214
+ ```vue
215
+ <div
216
+ v-fit-children="{ widthRestrictingContainer: containerRef, rowCount: 2 }"
217
+ style="flex-wrap: wrap; gap: 8px;"
218
+ >
219
+ <span v-for="tag in manyTags" :key="tag">{{ tag }}</span>
220
+ </div>
221
+ ```
222
+
223
+ The offset (`offsetNeededInPx`) is only reserved on the **last** row. All preceding rows use the full container width.
224
+
225
+ ## Inline "+N" badge
226
+
227
+ To keep the badge inline with the chips (instead of below), wrap both in a flex container and give the directive element `flex: 1`:
228
+
229
+ ```vue
230
+ <template>
231
+ <div ref="containerRef" style="display: flex; align-items: center; gap: 8px;">
232
+ <div
233
+ style="flex: 1; overflow: hidden;"
234
+ v-fit-children="{ widthRestrictingContainer: containerRef, offsetNeededInPx: 0 }"
235
+ @fit-children-updated="onUpdate"
236
+ >
237
+ <span v-for="tag in tags" :key="tag">{{ tag }}</span>
238
+ </div>
239
+ <span v-if="hiddenCount">+{{ hiddenCount }}</span>
240
+ </div>
241
+ </template>
242
+ ```
243
+
244
+ Set `offsetNeededInPx: 0` since the badge lives outside the directive element.
245
+
246
+ ## How it works
247
+
248
+ 1. On mount, the directive observes the `widthRestrictingContainer` for resize and the directive element for child list mutations.
249
+ 2. Individual children are also observed for size changes (e.g. an input growing as the user types).
250
+ 3. When triggered, it measures child widths via `getBoundingClientRect` (accounting for margins and `gap`). Measurements are cached and only re-computed when children are added, removed, or resized.
251
+ 4. If all children fit without needing the offset, everything stays visible — no "+N" badge is needed.
252
+ 5. Otherwise, candidates are sorted by priority (`keepVisibleEl` / `data-v-fit-keep` first), then by size (if `sortBySize`), then by DOM order.
253
+ 6. Children are placed row by row (up to `rowCount`). Offset is only reserved on the last row.
254
+ 7. A `fit-children-updated` event is dispatched so you can render a "+N more" indicator.
255
+ 8. Recalculations are batched via `requestAnimationFrame` + Vue's `nextTick` to avoid layout thrashing.
256
+
257
+ ## Known limitations
258
+
259
+ - The directive hides children using `display: none !important`. If a child has critical `display` styles set inline, they will be overridden while hidden.
260
+ - `keepVisibleEl` accepts a single element. To pin multiple children, use `data-v-fit-keep` on each. We won't hide them so it may break things
261
+
262
+ ## Browser support
263
+
264
+ Requires browsers that support `ResizeObserver`, `MutationObserver`, and `getBoundingClientRect`. All modern browsers (Chrome, Firefox, Safari, Edge) are supported.
265
+
266
+ ## License
267
+
268
+ MIT
@@ -0,0 +1,16 @@
1
+ import { type Directive } from "vue";
2
+ export type FitChildrenEventDetail = {
3
+ hiddenChildren: HTMLElement[];
4
+ hiddenChildrenCount: number;
5
+ isOverflowing: boolean;
6
+ };
7
+ export interface FitChildrenOptions {
8
+ gap?: number;
9
+ keepVisibleEl?: HTMLElement;
10
+ offsetNeededInPx?: number;
11
+ rowCount?: number;
12
+ sortBySize?: boolean;
13
+ widthRestrictingContainer?: HTMLElement;
14
+ }
15
+ export declare const vFitChildren: Directive;
16
+ //# sourceMappingURL=vFitChildren.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vFitChildren.d.ts","sourceRoot":"","sources":["../vFitChildren.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAmC,MAAM,KAAK,CAAC;AA8CtE,MAAM,MAAM,sBAAsB,GAAG;IACnC,cAAc,EAAE,WAAW,EAAE,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,WAAW,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yBAAyB,CAAC,EAAE,WAAW,CAAC;CACzC;AA8YD,eAAO,MAAM,YAAY,EAAE,SAyB1B,CAAC"}
@@ -0,0 +1,324 @@
1
+ import { nextTick } from "vue";
2
+ const DEFAULT_OFFSET_PX = 50;
3
+ const HIDDEN_ATTR = "data-v-fit-hidden";
4
+ const KEEP_ATTR = "data-v-fit-keep";
5
+ const EVENT_NAME = "fit-children-updated";
6
+ const stateMap = new WeakMap();
7
+ // ── Helpers ──────────────────────────────────────────────────────────
8
+ /** Parse a CSS pixel value, returning 0 for invalid/missing values. */
9
+ const parsePx = (value) => parseFloat(value) || 0;
10
+ /** Total horizontal overhead of an element (margin + border + padding). */
11
+ const getHorizontalOverhead = (style) => parsePx(style.marginLeft) +
12
+ parsePx(style.marginRight) +
13
+ parsePx(style.borderLeftWidth) +
14
+ parsePx(style.borderRightWidth) +
15
+ parsePx(style.paddingLeft) +
16
+ parsePx(style.paddingRight);
17
+ /** Content width of an element (inner width, excluding border and padding). */
18
+ const getContentWidth = (el, style = window.getComputedStyle(el)) => el.getBoundingClientRect().width -
19
+ parsePx(style.borderLeftWidth) -
20
+ parsePx(style.borderRightWidth) -
21
+ parsePx(style.paddingLeft) -
22
+ parsePx(style.paddingRight);
23
+ /** Outer width of an element (bounding rect width + horizontal margins). */
24
+ const getOuterWidth = (el) => {
25
+ const style = window.getComputedStyle(el);
26
+ return (el.getBoundingClientRect().width +
27
+ parsePx(style.marginLeft) +
28
+ parsePx(style.marginRight));
29
+ };
30
+ /** Whether a child should be kept visible (pinned via attribute or option). */
31
+ const isKeptChild = (child, keepVisibleEl) => child.hasAttribute(KEEP_ATTR) ||
32
+ (!!keepVisibleEl &&
33
+ (child === keepVisibleEl || child.contains(keepVisibleEl)));
34
+ // ── Visibility ───────────────────────────────────────────────────────
35
+ const dispatchUpdate = (el, detail) => {
36
+ el.dispatchEvent(new CustomEvent(EVENT_NAME, { detail }));
37
+ };
38
+ const showChild = (child) => {
39
+ // If explicitly hidden by us, or currently display:none, reveal it.
40
+ if (child.style.display === "none" || child.hasAttribute(HIDDEN_ATTR)) {
41
+ child.style.removeProperty("display");
42
+ child.removeAttribute(HIDDEN_ATTR);
43
+ }
44
+ };
45
+ const hideChild = (child) => {
46
+ if (child.style.display !== "none") {
47
+ child.style.setProperty("display", "none", "important");
48
+ child.setAttribute(HIDDEN_ATTR, "true");
49
+ }
50
+ };
51
+ // ── Measurement ──────────────────────────────────────────────────────
52
+ const updateMeasurements = (state) => {
53
+ const { targetElement: el } = state;
54
+ if (!el)
55
+ return;
56
+ const children = Array.from(el.children);
57
+ // Temporarily reveal hidden children so measurements are accurate
58
+ const previouslyHidden = el.querySelectorAll(`[${HIDDEN_ATTR}]`);
59
+ previouslyHidden.forEach((node) => {
60
+ const child = node;
61
+ child.style.display = "";
62
+ child.removeAttribute(HIDDEN_ATTR);
63
+ });
64
+ const style = window.getComputedStyle(el);
65
+ const computedGap = parsePx(style.columnGap || style.gap || "0");
66
+ state.gapPx =
67
+ state.gapFromOption !== undefined ? state.gapFromOption : computedGap;
68
+ state.cachedWidths = children.map(getOuterWidth);
69
+ state.totalChildWidth = state.cachedWidths.reduce((sum, w, i) => {
70
+ return sum + w + (i > 0 ? state.gapPx : 0);
71
+ }, 0);
72
+ state.cacheValid = true;
73
+ };
74
+ // ── Overflow calculation ─────────────────────────────────────────────
75
+ const calculateOverflow = (targetElement) => {
76
+ if (!targetElement)
77
+ return;
78
+ const state = stateMap.get(targetElement);
79
+ if (!state)
80
+ return;
81
+ const { parentContainer, targetElement: el, offsetNeededInPx, sortBySize, rowCount, keepVisibleEl, } = state;
82
+ if (!el || !parentContainer) {
83
+ state.rafId = null;
84
+ return;
85
+ }
86
+ let availableSpaceForChildren = getContentWidth(parentContainer);
87
+ if (parentContainer !== el) {
88
+ availableSpaceForChildren -= getHorizontalOverhead(window.getComputedStyle(el));
89
+ }
90
+ const immediateChildren = Array.from(el.children);
91
+ if (!state.cacheValid ||
92
+ state.cachedWidths.length !== immediateChildren.length) {
93
+ updateMeasurements(state);
94
+ }
95
+ // If all children fit without offset, show everything (no "+N" badge needed)
96
+ if (state.totalChildWidth <= availableSpaceForChildren) {
97
+ immediateChildren.forEach(showChild);
98
+ dispatchUpdate(el, {
99
+ hiddenChildren: [],
100
+ hiddenChildrenCount: 0,
101
+ isOverflowing: false,
102
+ });
103
+ state.rafId = null;
104
+ return;
105
+ }
106
+ // Build candidate indices sorted by priority: kept first, then by size/DOM order
107
+ const candidates = Array.from(immediateChildren.keys()).sort((a, b) => {
108
+ const aKeep = isKeptChild(immediateChildren[a], keepVisibleEl);
109
+ const bKeep = isKeptChild(immediateChildren[b], keepVisibleEl);
110
+ if (aKeep && !bKeep)
111
+ return -1;
112
+ if (!aKeep && bKeep)
113
+ return 1;
114
+ if (sortBySize) {
115
+ return state.cachedWidths[a] - state.cachedWidths[b];
116
+ }
117
+ return a - b;
118
+ });
119
+ // Pack children row-by-row; offset is only reserved on the last row
120
+ const hiddenChildren = [];
121
+ const visibleIndices = new Set();
122
+ const strictWidthLastRow = availableSpaceForChildren - offsetNeededInPx;
123
+ let usedWidth = 0;
124
+ let currentLine = 1;
125
+ for (const index of candidates) {
126
+ const itemWidth = state.cachedWidths[index];
127
+ const child = immediateChildren[index];
128
+ let gap = usedWidth === 0 ? 0 : state.gapPx;
129
+ let limit = currentLine === rowCount
130
+ ? strictWidthLastRow
131
+ : availableSpaceForChildren;
132
+ // If it doesn't fit current line, try next line
133
+ if (usedWidth + gap + itemWidth > limit) {
134
+ if (currentLine < rowCount) {
135
+ currentLine++;
136
+ usedWidth = 0;
137
+ gap = 0;
138
+ limit =
139
+ currentLine === rowCount
140
+ ? strictWidthLastRow
141
+ : availableSpaceForChildren;
142
+ }
143
+ }
144
+ if (usedWidth + gap + itemWidth <= limit) {
145
+ usedWidth += gap + itemWidth;
146
+ visibleIndices.add(index);
147
+ }
148
+ else {
149
+ // Does not fit on any available line
150
+ if (isKeptChild(child, keepVisibleEl)) {
151
+ usedWidth += gap + itemWidth;
152
+ visibleIndices.add(index);
153
+ }
154
+ else if (!sortBySize) {
155
+ break;
156
+ }
157
+ }
158
+ }
159
+ immediateChildren.forEach((child, i) => {
160
+ if (visibleIndices.has(i)) {
161
+ showChild(child);
162
+ }
163
+ else {
164
+ hideChild(child);
165
+ hiddenChildren.push(child);
166
+ }
167
+ });
168
+ dispatchUpdate(el, {
169
+ hiddenChildren,
170
+ hiddenChildrenCount: hiddenChildren.length,
171
+ isOverflowing: true,
172
+ });
173
+ state.rafId = null;
174
+ };
175
+ // ── Scheduling ───────────────────────────────────────────────────────
176
+ const scheduleOverflowCalculation = (state, invalidateCache = false) => {
177
+ if (invalidateCache)
178
+ state.cacheValid = false;
179
+ if (state.rafId || !state.targetElement)
180
+ return;
181
+ state.rafId = requestAnimationFrame(() => {
182
+ nextTick(() => {
183
+ calculateOverflow(state.targetElement);
184
+ });
185
+ });
186
+ };
187
+ const handleChildResize = (entries, state) => {
188
+ if (!state.cacheValid || !state.targetElement)
189
+ return;
190
+ const children = Array.from(state.targetElement.children);
191
+ const needsRecalc = entries.some((entry) => {
192
+ const index = children.indexOf(entry.target);
193
+ if (index === -1)
194
+ return false;
195
+ const currentOuterWidth = getOuterWidth(entry.target);
196
+ return Math.abs(currentOuterWidth - (state.cachedWidths[index] || 0)) > 1;
197
+ });
198
+ if (needsRecalc) {
199
+ scheduleOverflowCalculation(state, true);
200
+ }
201
+ };
202
+ // ── Directive lifecycle ──────────────────────────────────────────────
203
+ function handleFitChildren(wrapperEl, binding) {
204
+ let state = stateMap.get(wrapperEl);
205
+ if (!state) {
206
+ state = {
207
+ cachedWidths: [],
208
+ cacheValid: false,
209
+ childResizeObserver: undefined,
210
+ gapFromOption: binding.value?.gap,
211
+ gapPx: 0,
212
+ keepVisibleEl: binding.value?.keepVisibleEl,
213
+ mutationObserver: undefined,
214
+ offsetNeededInPx: Math.max(binding.value?.offsetNeededInPx ?? DEFAULT_OFFSET_PX, 0),
215
+ parentContainer: undefined,
216
+ rafId: null,
217
+ resizeObserver: undefined,
218
+ rowCount: binding.value?.rowCount ?? 1,
219
+ sortBySize: binding.value?.sortBySize ?? false,
220
+ targetElement: undefined,
221
+ totalChildWidth: 0,
222
+ };
223
+ stateMap.set(wrapperEl, state);
224
+ }
225
+ else {
226
+ let needsUpdate = false;
227
+ if (binding.value?.gap !== state.gapFromOption) {
228
+ state.gapFromOption = binding.value?.gap;
229
+ needsUpdate = true;
230
+ }
231
+ if (binding.value?.offsetNeededInPx !== undefined &&
232
+ binding.value.offsetNeededInPx !== state.offsetNeededInPx) {
233
+ state.offsetNeededInPx = Math.max(binding.value.offsetNeededInPx, 0);
234
+ needsUpdate = true;
235
+ }
236
+ if (binding.value?.sortBySize !== undefined &&
237
+ binding.value.sortBySize !== state.sortBySize) {
238
+ state.sortBySize = binding.value.sortBySize;
239
+ needsUpdate = true;
240
+ }
241
+ if (binding.value?.rowCount !== undefined &&
242
+ binding.value.rowCount !== state.rowCount) {
243
+ state.rowCount = Math.max(binding.value.rowCount, 1);
244
+ needsUpdate = true;
245
+ }
246
+ if (binding.value?.keepVisibleEl !== state.keepVisibleEl) {
247
+ state.keepVisibleEl = binding.value?.keepVisibleEl;
248
+ needsUpdate = true;
249
+ }
250
+ if (needsUpdate) {
251
+ scheduleOverflowCalculation(state, false);
252
+ }
253
+ }
254
+ if (!state.targetElement && wrapperEl) {
255
+ state.targetElement = wrapperEl;
256
+ if (!state.childResizeObserver) {
257
+ const childResizeObserver = new ResizeObserver((entries) => handleChildResize(entries, state));
258
+ Array.from(state.targetElement.children).forEach((child) => childResizeObserver.observe(child));
259
+ state.childResizeObserver = childResizeObserver;
260
+ }
261
+ if (!state.mutationObserver) {
262
+ const mutationObserver = new MutationObserver((mutations) => {
263
+ if (!state)
264
+ return;
265
+ mutations.forEach((m) => {
266
+ m.addedNodes.forEach((node) => {
267
+ if (node instanceof Element) {
268
+ state.childResizeObserver?.observe(node);
269
+ }
270
+ });
271
+ m.removedNodes.forEach((node) => {
272
+ if (node instanceof Element) {
273
+ state.childResizeObserver?.unobserve(node);
274
+ }
275
+ });
276
+ });
277
+ scheduleOverflowCalculation(state, true);
278
+ });
279
+ mutationObserver.observe(state.targetElement, { childList: true });
280
+ state.mutationObserver = mutationObserver;
281
+ }
282
+ }
283
+ // Use provided container or fallback to the directive's element
284
+ const container = binding.value?.widthRestrictingContainer || wrapperEl;
285
+ if (!state.parentContainer || state.parentContainer !== container) {
286
+ if (state.resizeObserver) {
287
+ state.resizeObserver.disconnect();
288
+ state.resizeObserver = undefined;
289
+ }
290
+ state.parentContainer = container;
291
+ if (!state.resizeObserver) {
292
+ const resizeObserver = new ResizeObserver(() => scheduleOverflowCalculation(state));
293
+ resizeObserver.observe(container);
294
+ state.resizeObserver = resizeObserver;
295
+ }
296
+ }
297
+ // Ensure flex-wrap is enabled if multiple rows are allowed
298
+ if (state.rowCount > 1 && wrapperEl.style.flexWrap !== "wrap") {
299
+ wrapperEl.style.setProperty("flex-wrap", "wrap");
300
+ }
301
+ }
302
+ export const vFitChildren = {
303
+ beforeMount: handleFitChildren,
304
+ beforeUnmount(el) {
305
+ const state = stateMap.get(el);
306
+ if (!state)
307
+ return;
308
+ if (state.rafId) {
309
+ cancelAnimationFrame(state.rafId);
310
+ }
311
+ if (state.mutationObserver) {
312
+ state.mutationObserver.disconnect();
313
+ }
314
+ if (state.resizeObserver) {
315
+ state.resizeObserver.disconnect();
316
+ }
317
+ if (state.childResizeObserver) {
318
+ state.childResizeObserver.disconnect();
319
+ }
320
+ stateMap.delete(el);
321
+ },
322
+ updated: handleFitChildren,
323
+ };
324
+ //# sourceMappingURL=vFitChildren.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vFitChildren.js","sourceRoot":"","sources":["../vFitChildren.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Directive, type DirectiveBinding, nextTick } from \"vue\";\n\n/**\n * A Vue directive that manages visibility of child elements within a container.\n *\n * Useful for chips, badges, tags, or any list of inline elements in a tight space.\n * Children that don't fit within the container's width are automatically hidden,\n * and a custom event is emitted with details about hidden children\n * (enabling \"+N more\" indicators).\n *\n * Features:\n * - Monitors the container's width via ResizeObserver\n * - Monitors individual child sizes via ResizeObserver\n * - Detects child additions/removals via MutationObserver\n * - Uses requestAnimationFrame for batched, performant recalculation\n * - Supports multi-row layout via `rowCount`\n * - Supports priority pinning via `keepVisibleEl` / `data-v-fit-keep`\n * - Supports hiding largest items first via `sortBySize`\n *\n * Usage:\n * <div v-fit-children=\"{ widthRestrictingContainer: containerRef, offsetNeededInPx: 50 }\">\n * <chip v-for=\"item in items\" />\n * </div>\n *\n * The directive emits a 'fit-children-updated' event with details about\n * hidden children count and overflow status.\n */\n\ninterface FitChildrenState {\n cachedWidths: number[];\n cacheValid: boolean;\n childResizeObserver?: ResizeObserver;\n gapFromOption?: number;\n gapPx: number;\n keepVisibleEl?: HTMLElement;\n mutationObserver?: MutationObserver;\n offsetNeededInPx: number;\n parentContainer?: HTMLElement;\n rafId: number | null;\n resizeObserver?: ResizeObserver;\n rowCount: number;\n sortBySize: boolean;\n targetElement?: HTMLElement;\n totalChildWidth: number;\n}\n\nexport type FitChildrenEventDetail = {\n hiddenChildren: HTMLElement[];\n hiddenChildrenCount: number;\n isOverflowing: boolean;\n};\n\nexport interface FitChildrenOptions {\n gap?: number;\n keepVisibleEl?: HTMLElement;\n offsetNeededInPx?: number;\n rowCount?: number;\n sortBySize?: boolean;\n widthRestrictingContainer?: HTMLElement;\n}\n\nconst DEFAULT_OFFSET_PX = 50;\nconst HIDDEN_ATTR = \"data-v-fit-hidden\";\nconst KEEP_ATTR = \"data-v-fit-keep\";\nconst EVENT_NAME = \"fit-children-updated\";\n\nconst stateMap = new WeakMap<HTMLElement, FitChildrenState>();\n\n// ── Helpers ──────────────────────────────────────────────────────────\n\n/** Parse a CSS pixel value, returning 0 for invalid/missing values. */\nconst parsePx = (value: string): number => parseFloat(value) || 0;\n\n/** Total horizontal overhead of an element (margin + border + padding). */\nconst getHorizontalOverhead = (style: CSSStyleDeclaration): number =>\n parsePx(style.marginLeft) +\n parsePx(style.marginRight) +\n parsePx(style.borderLeftWidth) +\n parsePx(style.borderRightWidth) +\n parsePx(style.paddingLeft) +\n parsePx(style.paddingRight);\n\n/** Content width of an element (inner width, excluding border and padding). */\nconst getContentWidth = (\n el: HTMLElement,\n style: CSSStyleDeclaration = window.getComputedStyle(el),\n): number =>\n el.getBoundingClientRect().width -\n parsePx(style.borderLeftWidth) -\n parsePx(style.borderRightWidth) -\n parsePx(style.paddingLeft) -\n parsePx(style.paddingRight);\n\n/** Outer width of an element (bounding rect width + horizontal margins). */\nconst getOuterWidth = (el: HTMLElement): number => {\n const style = window.getComputedStyle(el);\n return (\n el.getBoundingClientRect().width +\n parsePx(style.marginLeft) +\n parsePx(style.marginRight)\n );\n};\n\n/** Whether a child should be kept visible (pinned via attribute or option). */\nconst isKeptChild = (\n child: HTMLElement,\n keepVisibleEl?: HTMLElement,\n): boolean =>\n child.hasAttribute(KEEP_ATTR) ||\n (!!keepVisibleEl &&\n (child === keepVisibleEl || child.contains(keepVisibleEl)));\n\n// ── Visibility ───────────────────────────────────────────────────────\n\nconst dispatchUpdate = (el: HTMLElement, detail: FitChildrenEventDetail) => {\n el.dispatchEvent(\n new CustomEvent<FitChildrenEventDetail>(EVENT_NAME, { detail }),\n );\n};\n\nconst showChild = (child: HTMLElement) => {\n // If explicitly hidden by us, or currently display:none, reveal it.\n if (child.style.display === \"none\" || child.hasAttribute(HIDDEN_ATTR)) {\n child.style.removeProperty(\"display\");\n child.removeAttribute(HIDDEN_ATTR);\n }\n};\n\nconst hideChild = (child: HTMLElement) => {\n if (child.style.display !== \"none\") {\n child.style.setProperty(\"display\", \"none\", \"important\");\n child.setAttribute(HIDDEN_ATTR, \"true\");\n }\n};\n\n// ── Measurement ──────────────────────────────────────────────────────\n\nconst updateMeasurements = (state: FitChildrenState) => {\n const { targetElement: el } = state;\n if (!el) return;\n\n const children = Array.from(el.children) as HTMLElement[];\n\n // Temporarily reveal hidden children so measurements are accurate\n const previouslyHidden = el.querySelectorAll(`[${HIDDEN_ATTR}]`);\n previouslyHidden.forEach((node) => {\n const child = node as HTMLElement;\n child.style.display = \"\";\n child.removeAttribute(HIDDEN_ATTR);\n });\n\n const style = window.getComputedStyle(el);\n const computedGap = parsePx(style.columnGap || style.gap || \"0\");\n state.gapPx =\n state.gapFromOption !== undefined ? state.gapFromOption : computedGap;\n\n state.cachedWidths = children.map(getOuterWidth);\n\n state.totalChildWidth = state.cachedWidths.reduce((sum, w, i) => {\n return sum + w + (i > 0 ? state.gapPx : 0);\n }, 0);\n\n state.cacheValid = true;\n};\n\n// ── Overflow calculation ─────────────────────────────────────────────\n\nconst calculateOverflow = (targetElement: HTMLElement | undefined) => {\n if (!targetElement) return;\n\n const state = stateMap.get(targetElement);\n if (!state) return;\n\n const {\n parentContainer,\n targetElement: el,\n offsetNeededInPx,\n sortBySize,\n rowCount,\n keepVisibleEl,\n } = state;\n\n if (!el || !parentContainer) {\n state.rafId = null;\n return;\n }\n\n let availableSpaceForChildren = getContentWidth(parentContainer);\n\n if (parentContainer !== el) {\n availableSpaceForChildren -= getHorizontalOverhead(\n window.getComputedStyle(el),\n );\n }\n\n const immediateChildren = Array.from(el.children) as HTMLElement[];\n\n if (\n !state.cacheValid ||\n state.cachedWidths.length !== immediateChildren.length\n ) {\n updateMeasurements(state);\n }\n\n // If all children fit without offset, show everything (no \"+N\" badge needed)\n if (state.totalChildWidth <= availableSpaceForChildren) {\n immediateChildren.forEach(showChild);\n dispatchUpdate(el, {\n hiddenChildren: [],\n hiddenChildrenCount: 0,\n isOverflowing: false,\n });\n state.rafId = null;\n return;\n }\n\n // Build candidate indices sorted by priority: kept first, then by size/DOM order\n const candidates = Array.from(immediateChildren.keys()).sort((a, b) => {\n const aKeep = isKeptChild(immediateChildren[a], keepVisibleEl);\n const bKeep = isKeptChild(immediateChildren[b], keepVisibleEl);\n\n if (aKeep && !bKeep) return -1;\n if (!aKeep && bKeep) return 1;\n\n if (sortBySize) {\n return state.cachedWidths[a] - state.cachedWidths[b];\n }\n return a - b;\n });\n\n // Pack children row-by-row; offset is only reserved on the last row\n const hiddenChildren: HTMLElement[] = [];\n const visibleIndices = new Set<number>();\n const strictWidthLastRow = availableSpaceForChildren - offsetNeededInPx;\n let usedWidth = 0;\n let currentLine = 1;\n\n for (const index of candidates) {\n const itemWidth = state.cachedWidths[index];\n const child = immediateChildren[index];\n\n let gap = usedWidth === 0 ? 0 : state.gapPx;\n let limit =\n currentLine === rowCount\n ? strictWidthLastRow\n : availableSpaceForChildren;\n\n // If it doesn't fit current line, try next line\n if (usedWidth + gap + itemWidth > limit) {\n if (currentLine < rowCount) {\n currentLine++;\n usedWidth = 0;\n gap = 0;\n limit =\n currentLine === rowCount\n ? strictWidthLastRow\n : availableSpaceForChildren;\n }\n }\n\n if (usedWidth + gap + itemWidth <= limit) {\n usedWidth += gap + itemWidth;\n visibleIndices.add(index);\n } else {\n // Does not fit on any available line\n if (isKeptChild(child, keepVisibleEl)) {\n usedWidth += gap + itemWidth;\n visibleIndices.add(index);\n } else if (!sortBySize) {\n break;\n }\n }\n }\n\n immediateChildren.forEach((child, i) => {\n if (visibleIndices.has(i)) {\n showChild(child);\n } else {\n hideChild(child);\n hiddenChildren.push(child);\n }\n });\n\n dispatchUpdate(el, {\n hiddenChildren,\n hiddenChildrenCount: hiddenChildren.length,\n isOverflowing: true,\n });\n state.rafId = null;\n};\n\n// ── Scheduling ───────────────────────────────────────────────────────\n\nconst scheduleOverflowCalculation = (\n state: FitChildrenState,\n invalidateCache = false,\n) => {\n if (invalidateCache) state.cacheValid = false;\n\n if (state.rafId || !state.targetElement) return;\n\n state.rafId = requestAnimationFrame(() => {\n nextTick(() => {\n calculateOverflow(state.targetElement);\n });\n });\n};\n\nconst handleChildResize = (\n entries: ResizeObserverEntry[],\n state: FitChildrenState,\n) => {\n if (!state.cacheValid || !state.targetElement) return;\n\n const children = Array.from(state.targetElement.children);\n\n const needsRecalc = entries.some((entry) => {\n const index = children.indexOf(entry.target);\n if (index === -1) return false;\n const currentOuterWidth = getOuterWidth(entry.target as HTMLElement);\n return Math.abs(currentOuterWidth - (state.cachedWidths[index] || 0)) > 1;\n });\n\n if (needsRecalc) {\n scheduleOverflowCalculation(state, true);\n }\n};\n\n// ── Directive lifecycle ──────────────────────────────────────────────\n\nfunction handleFitChildren(\n wrapperEl: HTMLElement,\n binding: DirectiveBinding<FitChildrenOptions>,\n) {\n let state = stateMap.get(wrapperEl);\n\n if (!state) {\n state = {\n cachedWidths: [],\n cacheValid: false,\n childResizeObserver: undefined,\n gapFromOption: binding.value?.gap,\n gapPx: 0,\n keepVisibleEl: binding.value?.keepVisibleEl,\n mutationObserver: undefined,\n offsetNeededInPx: Math.max(\n binding.value?.offsetNeededInPx ?? DEFAULT_OFFSET_PX,\n 0,\n ),\n parentContainer: undefined,\n rafId: null,\n resizeObserver: undefined,\n rowCount: binding.value?.rowCount ?? 1,\n sortBySize: binding.value?.sortBySize ?? false,\n targetElement: undefined,\n totalChildWidth: 0,\n };\n stateMap.set(wrapperEl, state);\n } else {\n let needsUpdate = false;\n if (binding.value?.gap !== state.gapFromOption) {\n state.gapFromOption = binding.value?.gap;\n needsUpdate = true;\n }\n if (\n binding.value?.offsetNeededInPx !== undefined &&\n binding.value.offsetNeededInPx !== state.offsetNeededInPx\n ) {\n state.offsetNeededInPx = Math.max(binding.value.offsetNeededInPx, 0);\n needsUpdate = true;\n }\n if (\n binding.value?.sortBySize !== undefined &&\n binding.value.sortBySize !== state.sortBySize\n ) {\n state.sortBySize = binding.value.sortBySize;\n needsUpdate = true;\n }\n if (\n binding.value?.rowCount !== undefined &&\n binding.value.rowCount !== state.rowCount\n ) {\n state.rowCount = Math.max(binding.value.rowCount, 1);\n needsUpdate = true;\n }\n if (binding.value?.keepVisibleEl !== state.keepVisibleEl) {\n state.keepVisibleEl = binding.value?.keepVisibleEl;\n needsUpdate = true;\n }\n\n if (needsUpdate) {\n scheduleOverflowCalculation(state, false);\n }\n }\n\n if (!state.targetElement && wrapperEl) {\n state.targetElement = wrapperEl;\n\n if (!state.childResizeObserver) {\n const childResizeObserver = new ResizeObserver((entries) =>\n handleChildResize(entries, state!), // Safe because we just set stateMap\n );\n\n Array.from(state.targetElement.children).forEach((child) =>\n childResizeObserver.observe(child),\n );\n state.childResizeObserver = childResizeObserver;\n }\n\n if (!state.mutationObserver) {\n const mutationObserver = new MutationObserver((mutations) => {\n if (!state) return;\n mutations.forEach((m) => {\n m.addedNodes.forEach((node) => {\n if (node instanceof Element) {\n state.childResizeObserver?.observe(node);\n }\n });\n\n m.removedNodes.forEach((node) => {\n if (node instanceof Element) {\n state.childResizeObserver?.unobserve(node);\n }\n });\n });\n scheduleOverflowCalculation(state, true);\n });\n mutationObserver.observe(state.targetElement, { childList: true });\n state.mutationObserver = mutationObserver;\n }\n }\n\n // Use provided container or fallback to the directive's element\n const container = binding.value?.widthRestrictingContainer || wrapperEl;\n if (!state.parentContainer || state.parentContainer !== container) {\n if (state.resizeObserver) {\n state.resizeObserver.disconnect();\n state.resizeObserver = undefined;\n }\n\n state.parentContainer = container;\n\n if (!state.resizeObserver) {\n const resizeObserver = new ResizeObserver(() =>\n scheduleOverflowCalculation(state!),\n );\n resizeObserver.observe(container);\n state.resizeObserver = resizeObserver;\n }\n }\n\n // Ensure flex-wrap is enabled if multiple rows are allowed\n if (state.rowCount > 1 && wrapperEl.style.flexWrap !== \"wrap\") {\n wrapperEl.style.setProperty(\"flex-wrap\", \"wrap\");\n }\n}\n\nexport const vFitChildren: Directive = {\n beforeMount: handleFitChildren,\n beforeUnmount(el: HTMLElement) {\n const state = stateMap.get(el);\n if (!state) return;\n\n if (state.rafId) {\n cancelAnimationFrame(state.rafId);\n }\n\n if (state.mutationObserver) {\n state.mutationObserver.disconnect();\n }\n\n if (state.resizeObserver) {\n state.resizeObserver.disconnect();\n }\n\n if (state.childResizeObserver) {\n state.childResizeObserver.disconnect();\n }\n\n stateMap.delete(el);\n },\n updated: handleFitChildren,\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "v-fit-children",
3
+ "version": "1.0.0",
4
+ "description": "Vue directive that auto-hides children that don't fit within a container's width. Useful for chips, badges, tags, and other inline elements in tight spaces.",
5
+ "author": "Ozgur Seyidoglu",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/AzurIce/v-fit-children.git"
10
+ },
11
+ "homepage": "https://github.com/AzurIce/v-fit-children#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/AzurIce/v-fit-children/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "dist/vFitChildren.js",
17
+ "module": "dist/vFitChildren.js",
18
+ "types": "dist/vFitChildren.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/vFitChildren.js",
22
+ "types": "./dist/vFitChildren.d.ts"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "test": "vitest run",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "keywords": [
34
+ "vue",
35
+ "directive",
36
+ "overflow",
37
+ "hide",
38
+ "children",
39
+ "fit",
40
+ "chips",
41
+ "badges",
42
+ "tags",
43
+ "responsive",
44
+ "truncate",
45
+ "compact",
46
+ "vue3"
47
+ ],
48
+ "peerDependencies": {
49
+ "vue": "^3.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "jsdom": "^25.0.0",
53
+ "typescript": "^5.5.0",
54
+ "vitest": "^2.0.0",
55
+ "vue": "^3.0.0"
56
+ }
57
+ }