@falcondev-oss/nuxt-layers-base 0.42.0 → 0.42.2

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.
@@ -24,7 +24,7 @@ import {
24
24
  UOverflowButtons,
25
25
  USeparator,
26
26
  } from '#components'
27
- import { useAvailableWidth } from '../../composables/useAvailableWidth'
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 = useAvailableWidth(toolsAnchor)
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
- {...useAvailableWidth.root}
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
- {...useAvailableWidth.root}
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
- {...useAvailableWidth.free}
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
- {...useAvailableWidth.free}
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} {...useAvailableWidth.free} class="shrink-0">
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 { useAvailableWidth } from '../../composables/useAvailableWidth'
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 = useAvailableWidth(root)
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
@@ -1,10 +1,16 @@
1
+ import type { ClassValue } from 'tailwind-variants'
1
2
  import type { VNode } from 'vue'
3
+ import { cnMerge } from 'tailwind-variants'
2
4
 
3
5
  export default defineSetupComponent(
4
6
  (_: {
5
7
  props: {
6
8
  title?: string
7
9
  end?: boolean
10
+ ui?: {
11
+ root?: ClassValue
12
+ content?: ClassValue
13
+ }
8
14
  }
9
15
  slots: {
10
16
  default: () => VNode[]
@@ -13,24 +19,28 @@ export default defineSetupComponent(
13
19
  options(_, {
14
20
  name: 'URibbonSection',
15
21
  inheritAttrs: false,
16
- props: ['title', 'end'],
22
+ props: ['title', 'end', 'ui'],
17
23
  emits: [],
18
24
  setup:
19
25
  (props, { slots, attrs }) =>
20
26
  () => (
21
27
  <div
22
28
  data-end={props.end || undefined}
23
- class={[
29
+ class={cnMerge(
24
30
  'border-default -mt-px -ml-px flex min-w-fit flex-col gap-1.5 border-l px-3 py-2',
25
31
  // the row separator spans the full ribbon width (clipped by its overflow-hidden), so a
26
32
  // partially filled wrapped line still gets an unbroken line above it
27
33
  'before:border-default relative before:absolute before:inset-x-[-100vw] before:top-0 before:border-t',
28
34
  // end sections hug their content, so the leading sections absorb the spare space
29
35
  props.end ? 'flex-none' : 'flex-1',
30
- ]}
36
+ props.ui?.root,
37
+ )()}
31
38
  >
32
39
  {props.title ? <p class="text-dimmed text-[10px] leading-none">{props.title}</p> : null}
33
- <div class="flex flex-1 flex-wrap items-center gap-1" {...attrs}>
40
+ <div
41
+ class={cnMerge('flex flex-1 flex-wrap items-center gap-1', props.ui?.content)()}
42
+ {...attrs}
43
+ >
34
44
  {slots.default?.()}
35
45
  </div>
36
46
  </div>
@@ -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-available-width-free'
9
- const ROOT_ATTR = 'data-available-width-root'
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?.matches(FREE)) sibling = step(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?.matches(FREE)) return { el, side: 'right' }
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
- * useAvailableWidth.root
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 `useAvailableWidth.free` are read as empty space rather than as content
72
- * — the measurement runs straight through them, so `target` may grow into the room they hold. The
73
- * row containing `target` should carry `useAvailableWidth.root`: without it a target with nothing
74
- * to its left keeps climbing and ends up measured against something elsewhere on the page.
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 useAvailableWidth(target: MaybeComputedElementRef) {
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
- useAvailableWidth.root = { [ROOT_ATTR]: '' }
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
- useAvailableWidth.free = { [FREE_ATTR]: '' }
135
+ useFreeSpace.free = { [FREE_ATTR]: '' }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@falcondev-oss/nuxt-layers-base",
3
3
  "type": "module",
4
- "version": "0.42.0",
4
+ "version": "0.42.2",
5
5
  "description": "Nuxt layer with lots of useful helpers and @nuxt/ui components",
6
6
  "license": "MIT",
7
7
  "repository": {