@falcondev-oss/nuxt-layers-base 0.42.1 → 0.42.3
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/app/components/layout/LayoutNavbar.tsx +8 -11
- package/app/components/u/UOverflowButtons.tsx +2 -2
- package/app/components/u/URibbonCard.tsx +4 -0
- package/app/components/u/URibbonSection.tsx +1 -3
- package/app/composables/{useAvailableWidth.ts → useFreeSpace.ts} +17 -12
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
UOverflowButtons,
|
|
25
25
|
USeparator,
|
|
26
26
|
} from '#components'
|
|
27
|
-
import {
|
|
27
|
+
import { useFreeSpace } from '../../composables/useFreeSpace'
|
|
28
28
|
import { toolbarToolsKey } from '../../composables/useToolbar'
|
|
29
29
|
import { mergeSlotClass } from '../../utils/ui'
|
|
30
30
|
|
|
@@ -107,7 +107,7 @@ export default defineSetupComponent(
|
|
|
107
107
|
// anchor that stays at the end of that toolbar, so the answer doesn't change once the
|
|
108
108
|
// tools have moved to a toolbar of their own — they'd have no way back.
|
|
109
109
|
const toolsAnchor = ref<HTMLElement>()
|
|
110
|
-
const toolsSpace =
|
|
110
|
+
const toolsSpace = useFreeSpace(toolsAnchor)
|
|
111
111
|
// only ever the inline row — the wrapped one is compact, and a width measured there
|
|
112
112
|
// would not be the width the tools need to come back up
|
|
113
113
|
const toolsRow = ref<HTMLElement>()
|
|
@@ -163,7 +163,7 @@ export default defineSetupComponent(
|
|
|
163
163
|
<ForwardSlots slots={navbarSlots.value}>
|
|
164
164
|
<UDashboardNavbar
|
|
165
165
|
ui={navbarUi.value}
|
|
166
|
-
{...
|
|
166
|
+
{...useFreeSpace.root}
|
|
167
167
|
class="bg-white"
|
|
168
168
|
title={props.navbar.title}
|
|
169
169
|
v-slots={vSlots(UDashboardNavbar, {
|
|
@@ -228,7 +228,7 @@ export default defineSetupComponent(
|
|
|
228
228
|
left: mergeSlotClass(props.toolbarUi?.left, 'grow'),
|
|
229
229
|
}),
|
|
230
230
|
}}
|
|
231
|
-
{...
|
|
231
|
+
{...useFreeSpace.root}
|
|
232
232
|
class={['bg-white', (props.tabs || props.tools?.left) && '*:first:-ml-2']}
|
|
233
233
|
v-slots={vSlots(UDashboardToolbar, {
|
|
234
234
|
...((props.tabs || props.tools?.left) && {
|
|
@@ -253,7 +253,7 @@ export default defineSetupComponent(
|
|
|
253
253
|
...(showDivider.value
|
|
254
254
|
? [
|
|
255
255
|
<USeparator
|
|
256
|
-
{...
|
|
256
|
+
{...useFreeSpace.free}
|
|
257
257
|
orientation="vertical"
|
|
258
258
|
class="h-7"
|
|
259
259
|
/>,
|
|
@@ -264,7 +264,7 @@ export default defineSetupComponent(
|
|
|
264
264
|
: [
|
|
265
265
|
<div
|
|
266
266
|
ref={toolsRow}
|
|
267
|
-
{...
|
|
267
|
+
{...useFreeSpace.free}
|
|
268
268
|
class="shrink-0"
|
|
269
269
|
>
|
|
270
270
|
{toolsMenu()}
|
|
@@ -273,10 +273,7 @@ export default defineSetupComponent(
|
|
|
273
273
|
]
|
|
274
274
|
: // right-aligned: held open here, for the divider to centre in
|
|
275
275
|
[
|
|
276
|
-
<div
|
|
277
|
-
{...useAvailableWidth.free}
|
|
278
|
-
class="flex grow justify-center"
|
|
279
|
-
>
|
|
276
|
+
<div {...useFreeSpace.free} class="flex grow justify-center">
|
|
280
277
|
{showDivider.value ? (
|
|
281
278
|
<USeparator orientation="vertical" class="h-7" />
|
|
282
279
|
) : null}
|
|
@@ -291,7 +288,7 @@ export default defineSetupComponent(
|
|
|
291
288
|
// as free whether they are in it or not, so they keep their way back
|
|
292
289
|
...(!props.tools?.left && !toolsWrapped.value
|
|
293
290
|
? [
|
|
294
|
-
<div ref={toolsRow} {...
|
|
291
|
+
<div ref={toolsRow} {...useFreeSpace.free} class="shrink-0">
|
|
295
292
|
{toolsMenu()}
|
|
296
293
|
</div>,
|
|
297
294
|
]
|
|
@@ -2,7 +2,7 @@ import type { VNode } from 'vue'
|
|
|
2
2
|
import { useResizeObserver } from '@vueuse/core'
|
|
3
3
|
import { Comment, Fragment, isVNode } from 'vue'
|
|
4
4
|
import { UButton, UPopover } from '#components'
|
|
5
|
-
import {
|
|
5
|
+
import { useFreeSpace } from '../../composables/useFreeSpace'
|
|
6
6
|
|
|
7
7
|
/** JSX children and `v-if`/`v-for` arrive nested — unpack them to reach the individual actions.
|
|
8
8
|
* A `v-if` that didn't take leaves a comment placeholder behind, which is not an action. */
|
|
@@ -38,7 +38,7 @@ export default defineSetupComponent(
|
|
|
38
38
|
emits: [],
|
|
39
39
|
setup: (_props, { slots }) => {
|
|
40
40
|
const root = ref<HTMLElement>()
|
|
41
|
-
const space =
|
|
41
|
+
const space = useFreeSpace(root)
|
|
42
42
|
|
|
43
43
|
const triggerWidth = ref(0)
|
|
44
44
|
/** Action widths by index, kept from when the action last stood in the row — one that has
|
|
@@ -49,6 +49,10 @@ export default defineSetupComponent(
|
|
|
49
49
|
>
|
|
50
50
|
{leadingSections}
|
|
51
51
|
|
|
52
|
+
{/* every section hugs its content, so the spare space collects here instead of
|
|
53
|
+
stretching the last leading section */}
|
|
54
|
+
<div class="flex-1" />
|
|
55
|
+
|
|
52
56
|
{endSections.length > 0 ? (
|
|
53
57
|
// one flex item, so the end sections wrap onto their own line as a group
|
|
54
58
|
// instead of splitting up — the leading sections give way first
|
|
@@ -27,12 +27,10 @@ export default defineSetupComponent(
|
|
|
27
27
|
<div
|
|
28
28
|
data-end={props.end || undefined}
|
|
29
29
|
class={cnMerge(
|
|
30
|
-
'border-default -mt-px -ml-px flex min-w-fit flex-col gap-1.5 border-l px-3 py-2',
|
|
30
|
+
'border-default -mt-px -ml-px flex min-w-fit flex-none flex-col gap-1.5 border-l px-3 py-2',
|
|
31
31
|
// the row separator spans the full ribbon width (clipped by its overflow-hidden), so a
|
|
32
32
|
// partially filled wrapped line still gets an unbroken line above it
|
|
33
33
|
'before:border-default relative before:absolute before:inset-x-[-100vw] before:top-0 before:border-t',
|
|
34
|
-
// end sections hug their content, so the leading sections absorb the spare space
|
|
35
|
-
props.end ? 'flex-none' : 'flex-1',
|
|
36
34
|
props.ui?.root,
|
|
37
35
|
)()}
|
|
38
36
|
>
|
|
@@ -5,18 +5,22 @@ import { unrefElement, useResizeObserver } from '@vueuse/core'
|
|
|
5
5
|
// eslint-disable-next-line unicorn/prefer-number-coercion
|
|
6
6
|
const px = (value: string) => Number.parseFloat(value)
|
|
7
7
|
|
|
8
|
-
const FREE_ATTR = 'data-
|
|
9
|
-
const ROOT_ATTR = 'data-
|
|
8
|
+
const FREE_ATTR = 'data-free-space'
|
|
9
|
+
const ROOT_ATTR = 'data-free-space-root'
|
|
10
10
|
|
|
11
11
|
const FREE = `[${FREE_ATTR}]`
|
|
12
12
|
const ROOT = `[${ROOT_ATTR}]`
|
|
13
13
|
|
|
14
|
+
/** Counts as free: opted out of the measurement, or taking up no room in it anyway — hidden,
|
|
15
|
+
* unrendered, or grown to nothing. */
|
|
16
|
+
const isFree = (el: Element) => el.matches(FREE) || el.getBoundingClientRect().width === 0
|
|
17
|
+
|
|
14
18
|
/** The element next to `node` on `side`, skipping the ones that count as free. */
|
|
15
19
|
function siblingOf(node: Element | null | undefined, side: 'left' | 'right') {
|
|
16
20
|
const step = (el: Element) =>
|
|
17
21
|
side === 'left' ? el.previousElementSibling : el.nextElementSibling
|
|
18
22
|
let sibling = node && step(node)
|
|
19
|
-
while (sibling
|
|
23
|
+
while (sibling && isFree(sibling)) sibling = step(sibling)
|
|
20
24
|
return (sibling ?? undefined) as HTMLElement | undefined
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -27,7 +31,7 @@ const rightOf = (node: Element | null | undefined) => siblingOf(node, 'right')
|
|
|
27
31
|
* opted-out children hands over to the last child that isn't; one made of nothing but those
|
|
28
32
|
* ends where it begins, however wide it grew. */
|
|
29
33
|
function contentEdge(el: HTMLElement): { el: HTMLElement; side: 'left' | 'right' } {
|
|
30
|
-
if (!el.lastElementChild
|
|
34
|
+
if (!el.lastElementChild || !isFree(el.lastElementChild)) return { el, side: 'right' }
|
|
31
35
|
const last = leftOf(el.lastElementChild)
|
|
32
36
|
return last ? contentEdge(last) : { el, side: 'left' }
|
|
33
37
|
}
|
|
@@ -59,7 +63,7 @@ function anchors(target: MaybeComputedElementRef) {
|
|
|
59
63
|
* nothing does. Stays meaningful when `target` overflows — unlike its own width, which is why it
|
|
60
64
|
* can decide what still fits.
|
|
61
65
|
*
|
|
62
|
-
*
|
|
66
|
+
* useFreeSpace.root
|
|
63
67
|
* ┌───────────────────────────────────────────────────────┐
|
|
64
68
|
* │ ┌───────────┐ ┌╌╌╌╌╌╌╌╌╌┐ ┌────────┐ ┌─────────┐ │
|
|
65
69
|
* │ │ neighbour │ ╎ free ╎ │ target │ │ blocker │ │
|
|
@@ -68,16 +72,17 @@ function anchors(target: MaybeComputedElementRef) {
|
|
|
68
72
|
* from to
|
|
69
73
|
* └─────── space ───────┘
|
|
70
74
|
*
|
|
71
|
-
* Elements spread with `
|
|
72
|
-
* —
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
+
* Elements spread with `useFreeSpace.free`, and ones of no width at all — hidden, unrendered, or
|
|
76
|
+
* collapsed — are read as empty space rather than as content: the measurement runs straight
|
|
77
|
+
* through them, so `target` may grow into the room they hold. The row containing `target` should
|
|
78
|
+
* carry `useFreeSpace.root`: without it a target with nothing to its left keeps climbing and ends
|
|
79
|
+
* up measured against something elsewhere on the page.
|
|
75
80
|
*
|
|
76
81
|
* Either neighbour may be missing, in which case that end falls back to the container's content
|
|
77
82
|
* box. `target` itself is never measured — which is what keeps the answer meaningful once it
|
|
78
83
|
* overflows, and what lets an element that has given way find its way back.
|
|
79
84
|
*/
|
|
80
|
-
export function
|
|
85
|
+
export function useFreeSpace(target: MaybeComputedElementRef) {
|
|
81
86
|
// until measured, whatever asks gets "plenty" and renders in full
|
|
82
87
|
const space = ref(Infinity)
|
|
83
88
|
|
|
@@ -123,8 +128,8 @@ export function useAvailableWidth(target: MaybeComputedElementRef) {
|
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
/** Spread onto the box the search for a target's neighbours stops at. */
|
|
126
|
-
|
|
131
|
+
useFreeSpace.root = { [ROOT_ATTR]: '' }
|
|
127
132
|
|
|
128
133
|
/** Spread onto an element that only fills room the others left over — a divider centred in the
|
|
129
134
|
* gap, say. It sits in the flow, but the width it covers still counts as free. */
|
|
130
|
-
|
|
135
|
+
useFreeSpace.free = { [FREE_ATTR]: '' }
|