@sparklingopinions/sparkling-ui 0.15.0 → 0.16.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/dist/components/Board/Board.js.map +1 -1
- package/dist/components/Board/Board.vue.d.ts +3 -0
- package/dist/components/Board/Board.vue_vue_type_script_setup_true_lang.js +66 -61
- package/dist/components/Board/Board.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/package.json +1 -1
- package/src/styles/tailwind.css +19 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Board.js","names":["_sfc_main","Board_vue_vue_type_script_setup_true_lang_default","Board_default"],"sources":["../../../src/components/Board/Board.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { onBeforeUnmount, ref, useId } from 'vue'\n\nimport Icon from '../Icon/Icon.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { BoardColumn } from './types'\n\nexport interface BoardProps<T> {\n columns: BoardColumn<T>[]\n /** The item field that identifies it. */\n rowKey?: keyof T & string\n /** Cards also reorder within a column, for records that carry a position. */\n sortable?: boolean\n /** Columns run to the bottom of the parent and scroll inside it; give the parent a height. False sizes them to their cards. */\n fill?: boolean\n loading?: boolean\n /** The columns' heading level, to fit the page's outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n /** Draws each card's border, padding and surface. False when the `card` slot draws its own. */\n cardFrame?: boolean\n /** Makes a card a button that opens on click, Enter and Space (`@card-click`). Without it, the card slot carries its\n * own link or buttons. */\n onCardClick?: (item: T) => void\n /** Adds a dashed \"New\" button at the foot of each column, shown on hover (`@new`). */\n onNew?: (column: BoardColumn<T>) => void\n newLabel?: string\n emptyLabel?: string\n}\n\nconst props = withDefaults(defineProps<BoardProps<T>>(), {\n rowKey: 'id' as never,\n fill: true,\n headingLevel: 3,\n cardFrame: true,\n onCardClick: undefined,\n onNew: undefined,\n newLabel: 'New',\n emptyLabel: 'No cards'\n})\n\nconst emit = defineEmits<{\n /** A card dropped into a column, at its new index there. Show it moved at once; put it back if saving fails. */\n move: [id: unknown, column: string, index: number]\n}>()\n\ndefineSlots<{\n /** A card's content; `item.name` as a title by default. */\n card?: (props: { item: T; column: BoardColumn<T> }) => unknown\n /** Replaces a column's count, e.g. its summed deal value. */\n count?: (props: { column: BoardColumn<T> }) => unknown\n}>()\n\nconst id = useId()\nconst idOf = (item: T): unknown => item[props.rowKey ?? 'id']\nconst root = ref<HTMLElement>()\n\nconst dragged = ref<{ id: unknown; column: string; index: number } | null>(null)\nconst target = ref<{ column: string; index: number } | null>(null)\n\n// Dragging runs on pointer events, so it works with a mouse, a pen and a finger. A mouse drags once it moves a few\n// pixels, so a click still clicks; a finger after a short press, so a swipe still scrolls.\nconst MOUSE_SLOP = 4\nconst TOUCH_SLOP = 8\nconst TOUCH_DELAY = 250\nconst EDGE = 48\n\nlet pending: {\n item: T\n column: string\n index: number\n x: number\n y: number\n card: HTMLElement\n touch: boolean\n} | null = null\nlet pressTimer: number | undefined\nlet ghost: HTMLElement | null = null\nlet grab = { x: 0, y: 0 }\nlet justDragged = false\n\nfunction onPointerDown(event: PointerEvent, item: T, column: string, index: number) {\n if (event.button !== 0 || (event.target as HTMLElement).closest('input, textarea, select, [contenteditable]')) return\n const touch = event.pointerType === 'touch'\n pending = { item, column, index, x: event.clientX, y: event.clientY, card: event.currentTarget as HTMLElement, touch }\n if (touch) pressTimer = window.setTimeout(() => pending && start(), TOUCH_DELAY)\n window.addEventListener('pointermove', onPointerMove)\n window.addEventListener('pointerup', onPointerUp)\n window.addEventListener('pointercancel', stop)\n window.addEventListener('touchmove', holdScroll, { passive: false })\n window.addEventListener('keydown', onEscape)\n}\n\nfunction start() {\n if (!pending) return\n const rect = pending.card.getBoundingClientRect()\n grab = { x: pending.x - rect.left, y: pending.y - rect.top }\n ghost = pending.card.cloneNode(true) as HTMLElement\n ghost.removeAttribute('id')\n ghost.setAttribute('aria-hidden', 'true')\n ghost.classList.add('pointer-events-none', 'fixed', 'top-0', 'left-0', 'z-[1300]', 'shadow-lg', 'opacity-95')\n ghost.style.width = `${rect.width}px`\n document.body.appendChild(ghost)\n follow(pending.x, pending.y)\n dragged.value = { id: idOf(pending.item), column: pending.column, index: pending.index }\n}\n\nfunction onPointerMove(event: PointerEvent) {\n if (!pending) return\n if (!dragged.value) {\n const moved = Math.hypot(event.clientX - pending.x, event.clientY - pending.y)\n // A finger that moves before the press is long enough is scrolling.\n if (pending.touch) return moved > TOUCH_SLOP ? stop() : undefined\n if (moved < MOUSE_SLOP) return\n start()\n }\n event.preventDefault()\n follow(event.clientX, event.clientY)\n aim(event.clientX, event.clientY)\n scrollNear(event.clientX, event.clientY)\n}\n\nfunction follow(x: number, y: number) {\n if (ghost) ghost.style.transform = `translate(${x - grab.x}px, ${y - grab.y}px) rotate(1.5deg)`\n}\n\n// What's under the pointer: a card's top half drops before it, the bottom half after; an empty spot, at the end.\nfunction aim(x: number, y: number) {\n const under = document.elementFromPoint(x, y)\n const body = under?.closest<HTMLElement>('[data-board-column]')\n if (!body || !root.value?.contains(body)) {\n target.value = null\n return\n }\n const column = body.dataset.boardColumn!\n const card = under!.closest<HTMLElement>('[data-board-index]')\n let index = props.columns.find((c) => c.id === column)?.items.length ?? 0\n if (card && body.contains(card)) {\n const rect = card.getBoundingClientRect()\n index = Number(card.dataset.boardIndex) + (y < rect.top + rect.height / 2 ? 0 : 1)\n }\n if (target.value?.column !== column || target.value.index !== index) target.value = { column, index }\n}\n\n// Near a column's top or bottom, or the board's sides, it scrolls that way.\nfunction scrollNear(x: number, y: number) {\n const body = document.elementFromPoint(x, y)?.closest<HTMLElement>('[data-board-column]')\n if (body) {\n const rect = body.getBoundingClientRect()\n if (y < rect.top + EDGE) body.scrollTop -= 12\n else if (y > rect.bottom - EDGE) body.scrollTop += 12\n }\n const board = root.value?.getBoundingClientRect()\n if (board && root.value) {\n if (x < board.left + EDGE) root.value.scrollLeft -= 12\n else if (x > board.right - EDGE) root.value.scrollLeft += 12\n }\n}\n\nfunction onPointerUp() {\n const from = dragged.value\n const to = target.value\n if (from && to) {\n let index = to.index\n if (from.column === to.column) {\n // Within a column the dragged card leaves its old slot first.\n if (index > from.index) index--\n if (props.sortable && index !== from.index) emit('move', from.id, to.column, index)\n } else {\n emit('move', from.id, to.column, index)\n }\n }\n stop()\n}\n\nfunction holdScroll(event: TouchEvent) {\n if (dragged.value) event.preventDefault()\n}\n\nfunction onEscape(event: KeyboardEvent) {\n if (event.key === 'Escape' && dragged.value) {\n event.preventDefault()\n stop()\n }\n}\n\nfunction stop() {\n window.clearTimeout(pressTimer)\n window.removeEventListener('pointermove', onPointerMove)\n window.removeEventListener('pointerup', onPointerUp)\n window.removeEventListener('pointercancel', stop)\n window.removeEventListener('touchmove', holdScroll)\n window.removeEventListener('keydown', onEscape)\n ghost?.remove()\n ghost = null\n // The click that ends a drag isn't a click on the card.\n if (dragged.value) {\n justDragged = true\n window.setTimeout(() => (justDragged = false))\n }\n pending = null\n dragged.value = null\n target.value = null\n}\n\nonBeforeUnmount(stop)\n\nfunction clickCard(event: MouseEvent, item: T) {\n if (justDragged) {\n event.preventDefault()\n event.stopPropagation()\n return\n }\n props.onCardClick?.(item)\n}\n\nfunction onCardKeydown(event: KeyboardEvent, item: T) {\n if (!props.onCardClick || event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return\n event.preventDefault()\n props.onCardClick(item)\n}\n\nconst isTarget = (column: string, index: number, item: T) =>\n target.value?.column === column && target.value.index === index && dragged.value?.id !== idOf(item)\n\nconst frame =\n 'border border-sui-edge bg-sui-raised p-4 transition-[border-color,opacity] duration-150 hover:border-sui-line-strong'\n</script>\n\n<!-- A board: cards in columns by a field (a stage, a status), dragged between columns. -->\n<template>\n <div\n ref=\"root\"\n class=\"flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']\"\n :class=\"fill && 'h-full min-h-0 flex-1'\"\n >\n <section\n v-for=\"column in columns\"\n :key=\"column.id\"\n :aria-labelledby=\"`${id}-${column.id}`\"\n class=\"flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col\"\n >\n <header class=\"flex items-center justify-between px-1 pb-2.5\">\n <component\n :is=\"`h${headingLevel}`\"\n :id=\"`${id}-${column.id}`\"\n class=\"m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink\"\n >\n <Icon v-if=\"column.icon\" :color=\"column.iconColor ?? 'ink-subtle'\" :name=\"column.icon\" />\n {{ column.name }}\n </component>\n <span\n class=\"ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-fill px-2 text-xs text-sui-ink-muted tabular-nums\"\n >\n <slot :column=\"column\" name=\"count\">{{ column.items.length }}</slot>\n </span>\n </header>\n <div\n class=\"group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl bg-sui-fill p-2.5 transition-shadow duration-150\"\n :class=\"target?.column === column.id && 'ring-2 ring-sui-accent-line ring-inset'\"\n :data-board-column=\"column.id\"\n >\n <template v-if=\"loading\">\n <div v-for=\"i in 3\" :key=\"i\" aria-hidden=\"true\" class=\"rounded-xl border border-sui-edge bg-sui-raised p-4\">\n <Skeleton class=\"mb-3\" />\n <Skeleton height=\"0.75rem\" width=\"70%\" />\n </div>\n </template>\n <p\n v-else-if=\"column.items.length === 0 && !dragged\"\n class=\"m-0 px-1 py-3 text-center text-13 text-sui-ink-muted\"\n >\n {{ emptyLabel }}\n </p>\n <ul v-else class=\"m-0 flex list-none flex-col gap-2.5 p-0\">\n <li v-for=\"(item, index) in column.items\" :key=\"idOf(item) as PropertyKey\">\n <div\n class=\"relative rounded-xl outline-hidden transition-opacity duration-150 select-none\"\n :class=\"[\n cardFrame && frame,\n onCardClick && 'cursor-pointer focus-visible:sui-ring',\n dragged?.id === idOf(item) && 'opacity-40',\n isTarget(column.id, index, item) && 'shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]'\n ]\"\n :data-board-index=\"index\"\n :role=\"onCardClick ? 'button' : undefined\"\n :tabindex=\"onCardClick ? 0 : undefined\"\n @click.capture=\"clickCard($event, item)\"\n @contextmenu=\"dragged && $event.preventDefault()\"\n @dragstart.prevent\n @keydown=\"onCardKeydown($event, item)\"\n @pointerdown=\"onPointerDown($event, item, column.id, index)\"\n >\n <slot :column=\"column\" :item=\"item\" name=\"card\">\n <p class=\"m-0 text-sm font-semibold text-sui-ink\">{{ item.name }}</p>\n </slot>\n </div>\n </li>\n </ul>\n <button\n v-if=\"onNew && !loading\"\n class=\"flex h-(--sui-control-height) w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring\"\n type=\"button\"\n @click=\"onNew(column)\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 14 14\"\n >\n <path d=\"M7 2.5v9M2.5 7h9\" />\n </svg>\n {{ newLabel }}\n </button>\n </div>\n </section>\n </div>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"Board.js","names":["_sfc_main","Board_vue_vue_type_script_setup_true_lang_default","Board_default"],"sources":["../../../src/components/Board/Board.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { computed, onBeforeUnmount, ref, useId } from 'vue'\n\nimport Icon from '../Icon/Icon.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { BoardColumn } from './types'\n\nexport interface BoardProps<T> {\n columns: BoardColumn<T>[]\n /** The item field that identifies it. */\n rowKey?: keyof T & string\n /** Cards also reorder within a column, for records that carry a position. */\n sortable?: boolean\n /** Columns run to the bottom of the parent and scroll inside it; give the parent a height. False sizes them to their cards. */\n fill?: boolean\n loading?: boolean\n /** The columns' heading level, to fit the page's outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n /** Draws each card's border, padding and surface. False when the `card` slot draws its own. */\n cardFrame?: boolean\n /** Makes a card a button that opens on click, Enter and Space (`@card-click`). Without it, the card slot carries its\n * own link or buttons. */\n onCardClick?: (item: T) => void\n /** Adds a dashed \"New\" button at the foot of each column, shown on hover (`@new`). */\n onNew?: (column: BoardColumn<T>) => void\n newLabel?: string\n emptyLabel?: string\n /** The columns' ground: a soft fill by default; `canvas` (the page's, with a hairline) or `sunken` match a Table's or\n * DataList's header tone, so the cards stand out on it. */\n columnTone?: 'fill' | 'canvas' | 'sunken'\n}\n\nconst props = withDefaults(defineProps<BoardProps<T>>(), {\n rowKey: 'id' as never,\n fill: true,\n headingLevel: 3,\n cardFrame: true,\n onCardClick: undefined,\n onNew: undefined,\n newLabel: 'New',\n emptyLabel: 'No cards',\n columnTone: 'fill'\n})\n\nconst emit = defineEmits<{\n /** A card dropped into a column, at its new index there. Show it moved at once; put it back if saving fails. */\n move: [id: unknown, column: string, index: number]\n}>()\n\ndefineSlots<{\n /** A card's content; `item.name` as a title by default. */\n card?: (props: { item: T; column: BoardColumn<T> }) => unknown\n /** Replaces a column's count, e.g. its summed deal value. */\n count?: (props: { column: BoardColumn<T> }) => unknown\n}>()\n\nconst id = useId()\nconst idOf = (item: T): unknown => item[props.rowKey ?? 'id']\nconst root = ref<HTMLElement>()\n\nconst dragged = ref<{ id: unknown; column: string; index: number } | null>(null)\nconst target = ref<{ column: string; index: number } | null>(null)\n\n// Dragging runs on pointer events, so it works with a mouse, a pen and a finger. A mouse drags once it moves a few\n// pixels, so a click still clicks; a finger after a short press, so a swipe still scrolls.\nconst MOUSE_SLOP = 4\nconst TOUCH_SLOP = 8\nconst TOUCH_DELAY = 250\nconst EDGE = 48\n\nlet pending: {\n item: T\n column: string\n index: number\n x: number\n y: number\n card: HTMLElement\n touch: boolean\n} | null = null\nlet pressTimer: number | undefined\nlet ghost: HTMLElement | null = null\nlet grab = { x: 0, y: 0 }\nlet justDragged = false\n\nfunction onPointerDown(event: PointerEvent, item: T, column: string, index: number) {\n if (event.button !== 0 || (event.target as HTMLElement).closest('input, textarea, select, [contenteditable]')) return\n const touch = event.pointerType === 'touch'\n pending = { item, column, index, x: event.clientX, y: event.clientY, card: event.currentTarget as HTMLElement, touch }\n if (touch) pressTimer = window.setTimeout(() => pending && start(), TOUCH_DELAY)\n window.addEventListener('pointermove', onPointerMove)\n window.addEventListener('pointerup', onPointerUp)\n window.addEventListener('pointercancel', stop)\n window.addEventListener('touchmove', holdScroll, { passive: false })\n window.addEventListener('keydown', onEscape)\n}\n\nfunction start() {\n if (!pending) return\n const rect = pending.card.getBoundingClientRect()\n grab = { x: pending.x - rect.left, y: pending.y - rect.top }\n ghost = pending.card.cloneNode(true) as HTMLElement\n ghost.removeAttribute('id')\n ghost.setAttribute('aria-hidden', 'true')\n ghost.classList.add('pointer-events-none', 'fixed', 'top-0', 'left-0', 'z-[1300]', 'shadow-lg', 'opacity-95')\n ghost.style.width = `${rect.width}px`\n document.body.appendChild(ghost)\n follow(pending.x, pending.y)\n dragged.value = { id: idOf(pending.item), column: pending.column, index: pending.index }\n}\n\nfunction onPointerMove(event: PointerEvent) {\n if (!pending) return\n if (!dragged.value) {\n const moved = Math.hypot(event.clientX - pending.x, event.clientY - pending.y)\n // A finger that moves before the press is long enough is scrolling.\n if (pending.touch) return moved > TOUCH_SLOP ? stop() : undefined\n if (moved < MOUSE_SLOP) return\n start()\n }\n event.preventDefault()\n follow(event.clientX, event.clientY)\n aim(event.clientX, event.clientY)\n scrollNear(event.clientX, event.clientY)\n}\n\nfunction follow(x: number, y: number) {\n if (ghost) ghost.style.transform = `translate(${x - grab.x}px, ${y - grab.y}px) rotate(1.5deg)`\n}\n\n// What's under the pointer: a card's top half drops before it, the bottom half after; an empty spot, at the end.\nfunction aim(x: number, y: number) {\n const under = document.elementFromPoint(x, y)\n const body = under?.closest<HTMLElement>('[data-board-column]')\n if (!body || !root.value?.contains(body)) {\n target.value = null\n return\n }\n const column = body.dataset.boardColumn!\n const card = under!.closest<HTMLElement>('[data-board-index]')\n let index = props.columns.find((c) => c.id === column)?.items.length ?? 0\n if (card && body.contains(card)) {\n const rect = card.getBoundingClientRect()\n index = Number(card.dataset.boardIndex) + (y < rect.top + rect.height / 2 ? 0 : 1)\n }\n if (target.value?.column !== column || target.value.index !== index) target.value = { column, index }\n}\n\n// Near a column's top or bottom, or the board's sides, it scrolls that way.\nfunction scrollNear(x: number, y: number) {\n const body = document.elementFromPoint(x, y)?.closest<HTMLElement>('[data-board-column]')\n if (body) {\n const rect = body.getBoundingClientRect()\n if (y < rect.top + EDGE) body.scrollTop -= 12\n else if (y > rect.bottom - EDGE) body.scrollTop += 12\n }\n const board = root.value?.getBoundingClientRect()\n if (board && root.value) {\n if (x < board.left + EDGE) root.value.scrollLeft -= 12\n else if (x > board.right - EDGE) root.value.scrollLeft += 12\n }\n}\n\nfunction onPointerUp() {\n const from = dragged.value\n const to = target.value\n if (from && to) {\n let index = to.index\n if (from.column === to.column) {\n // Within a column the dragged card leaves its old slot first.\n if (index > from.index) index--\n if (props.sortable && index !== from.index) emit('move', from.id, to.column, index)\n } else {\n emit('move', from.id, to.column, index)\n }\n }\n stop()\n}\n\nfunction holdScroll(event: TouchEvent) {\n if (dragged.value) event.preventDefault()\n}\n\nfunction onEscape(event: KeyboardEvent) {\n if (event.key === 'Escape' && dragged.value) {\n event.preventDefault()\n stop()\n }\n}\n\nfunction stop() {\n window.clearTimeout(pressTimer)\n window.removeEventListener('pointermove', onPointerMove)\n window.removeEventListener('pointerup', onPointerUp)\n window.removeEventListener('pointercancel', stop)\n window.removeEventListener('touchmove', holdScroll)\n window.removeEventListener('keydown', onEscape)\n ghost?.remove()\n ghost = null\n // The click that ends a drag isn't a click on the card.\n if (dragged.value) {\n justDragged = true\n window.setTimeout(() => (justDragged = false))\n }\n pending = null\n dragged.value = null\n target.value = null\n}\n\nonBeforeUnmount(stop)\n\nfunction clickCard(event: MouseEvent, item: T) {\n if (justDragged) {\n event.preventDefault()\n event.stopPropagation()\n return\n }\n props.onCardClick?.(item)\n}\n\nfunction onCardKeydown(event: KeyboardEvent, item: T) {\n if (!props.onCardClick || event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return\n event.preventDefault()\n props.onCardClick(item)\n}\n\nconst grounds = { fill: 'bg-sui-fill', canvas: 'border border-sui-line-subtle bg-sui-canvas', sunken: 'sui-band' }\nconst columnGround = computed(() => grounds[props.columnTone])\n\nconst isTarget = (column: string, index: number, item: T) =>\n target.value?.column === column && target.value.index === index && dragged.value?.id !== idOf(item)\n\nconst frame =\n 'border border-sui-edge bg-sui-raised p-4 transition-[border-color,opacity] duration-150 hover:border-sui-line-strong'\n</script>\n\n<!-- A board: cards in columns by a field (a stage, a status), dragged between columns. -->\n<template>\n <div\n ref=\"root\"\n class=\"flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']\"\n :class=\"fill && 'h-full min-h-0 flex-1'\"\n >\n <section\n v-for=\"column in columns\"\n :key=\"column.id\"\n :aria-labelledby=\"`${id}-${column.id}`\"\n class=\"flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col\"\n >\n <header class=\"flex items-center justify-between px-1 pb-2.5\">\n <component\n :is=\"`h${headingLevel}`\"\n :id=\"`${id}-${column.id}`\"\n class=\"m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink\"\n >\n <Icon v-if=\"column.icon\" :color=\"column.iconColor ?? 'ink-subtle'\" :name=\"column.icon\" />\n {{ column.name }}\n </component>\n <span\n class=\"ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-fill px-2 text-xs text-sui-ink-muted tabular-nums\"\n >\n <slot :column=\"column\" name=\"count\">{{ column.items.length }}</slot>\n </span>\n </header>\n <div\n class=\"group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl p-2.5 transition-shadow duration-150\"\n :class=\"[columnGround, target?.column === column.id && 'ring-2 ring-sui-accent-line ring-inset']\"\n :data-board-column=\"column.id\"\n >\n <template v-if=\"loading\">\n <div v-for=\"i in 3\" :key=\"i\" aria-hidden=\"true\" class=\"rounded-xl border border-sui-edge bg-sui-raised p-4\">\n <Skeleton class=\"mb-3\" />\n <Skeleton height=\"0.75rem\" width=\"70%\" />\n </div>\n </template>\n <p\n v-else-if=\"column.items.length === 0 && !dragged\"\n class=\"m-0 px-1 py-3 text-center text-13 text-sui-ink-muted\"\n >\n {{ emptyLabel }}\n </p>\n <ul v-else class=\"m-0 flex list-none flex-col gap-2.5 p-0\">\n <li v-for=\"(item, index) in column.items\" :key=\"idOf(item) as PropertyKey\">\n <div\n class=\"relative rounded-xl outline-hidden transition-opacity duration-150 select-none\"\n :class=\"[\n cardFrame && frame,\n onCardClick && 'cursor-pointer focus-visible:sui-ring',\n dragged?.id === idOf(item) && 'opacity-40',\n isTarget(column.id, index, item) && 'shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]'\n ]\"\n :data-board-index=\"index\"\n :role=\"onCardClick ? 'button' : undefined\"\n :tabindex=\"onCardClick ? 0 : undefined\"\n @click.capture=\"clickCard($event, item)\"\n @contextmenu=\"dragged && $event.preventDefault()\"\n @dragstart.prevent\n @keydown=\"onCardKeydown($event, item)\"\n @pointerdown=\"onPointerDown($event, item, column.id, index)\"\n >\n <slot :column=\"column\" :item=\"item\" name=\"card\">\n <p class=\"m-0 text-sm font-semibold text-sui-ink\">{{ item.name }}</p>\n </slot>\n </div>\n </li>\n </ul>\n <button\n v-if=\"onNew && !loading\"\n class=\"flex h-(--sui-control-height) w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring\"\n type=\"button\"\n @click=\"onNew(column)\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 14 14\"\n >\n <path d=\"M7 2.5v9M2.5 7h9\" />\n </svg>\n {{ newLabel }}\n </button>\n </div>\n </section>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -19,6 +19,9 @@ export interface BoardProps<T> {
|
|
|
19
19
|
onNew?: (column: BoardColumn<T>) => void;
|
|
20
20
|
newLabel?: string;
|
|
21
21
|
emptyLabel?: string;
|
|
22
|
+
/** The columns' ground: a soft fill by default; `canvas` (the page's, with a hairline) or `sunken` match a Table's or
|
|
23
|
+
* DataList's header tone, so the cards stand out on it. */
|
|
24
|
+
columnTone?: 'fill' | 'canvas' | 'sunken';
|
|
22
25
|
}
|
|
23
26
|
declare const __VLS_export: <T extends Record<string, any>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
24
27
|
props: import("vue").PublicProps & __VLS_PrettifyLocal<BoardProps<T> & {
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import e from "../Icon/Icon.js";
|
|
2
2
|
import t from "../Skeleton/Skeleton.js";
|
|
3
|
-
import { Fragment as n,
|
|
3
|
+
import { Fragment as n, computed as r, createBlock as i, createCommentVNode as a, createElementBlock as o, createElementVNode as s, createTextVNode as c, createVNode as l, defineComponent as u, normalizeClass as d, onBeforeUnmount as f, openBlock as p, ref as m, renderList as h, renderSlot as g, resolveDynamicComponent as _, toDisplayString as v, unref as y, useId as ee, withCtx as te, withModifiers as ne } from "vue";
|
|
4
4
|
//#region src/components/Board/Board.vue?vue&type=script&setup=true&lang.ts
|
|
5
|
-
var
|
|
5
|
+
var re = ["aria-labelledby"], b = { class: "flex items-center justify-between px-1 pb-2.5" }, x = { class: "ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-fill px-2 text-xs text-sui-ink-muted tabular-nums" }, S = ["data-board-column"], C = {
|
|
6
6
|
key: 1,
|
|
7
7
|
class: "m-0 px-1 py-3 text-center text-13 text-sui-ink-muted"
|
|
8
|
-
},
|
|
8
|
+
}, w = {
|
|
9
9
|
key: 2,
|
|
10
10
|
class: "m-0 flex list-none flex-col gap-2.5 p-0"
|
|
11
|
-
},
|
|
11
|
+
}, T = [
|
|
12
12
|
"data-board-index",
|
|
13
13
|
"role",
|
|
14
14
|
"tabindex",
|
|
15
15
|
"onClickCapture",
|
|
16
16
|
"onKeydown",
|
|
17
17
|
"onPointerdown"
|
|
18
|
-
],
|
|
18
|
+
], E = { class: "m-0 text-sm font-semibold text-sui-ink" }, D = ["onClick"], O = 4, k = 8, A = 250, j = 48, ie = "border border-sui-edge bg-sui-raised p-4 transition-[border-color,opacity] duration-150 hover:border-sui-line-strong", M = /*@__PURE__*/ u({
|
|
19
19
|
__name: "Board",
|
|
20
20
|
props: {
|
|
21
21
|
columns: {},
|
|
@@ -40,11 +40,12 @@ var te = ["aria-labelledby"], ne = { class: "flex items-center justify-between p
|
|
|
40
40
|
default: void 0
|
|
41
41
|
},
|
|
42
42
|
newLabel: { default: "New" },
|
|
43
|
-
emptyLabel: { default: "No cards" }
|
|
43
|
+
emptyLabel: { default: "No cards" },
|
|
44
|
+
columnTone: { default: "fill" }
|
|
44
45
|
},
|
|
45
46
|
emits: ["move"],
|
|
46
|
-
setup(
|
|
47
|
-
let N =
|
|
47
|
+
setup(u, { emit: M }) {
|
|
48
|
+
let N = u, P = M, F = ee(), I = (e) => e[N.rowKey ?? "id"], L = m(), R = m(null), z = m(null), B = null, V, H = null, U = {
|
|
48
49
|
x: 0,
|
|
49
50
|
y: 0
|
|
50
51
|
}, W = !1;
|
|
@@ -59,7 +60,7 @@ var te = ["aria-labelledby"], ne = { class: "flex items-center justify-between p
|
|
|
59
60
|
y: e.clientY,
|
|
60
61
|
card: e.currentTarget,
|
|
61
62
|
touch: i
|
|
62
|
-
}, i && (V = window.setTimeout(() => B && K(),
|
|
63
|
+
}, i && (V = window.setTimeout(() => B && K(), A)), window.addEventListener("pointermove", q), window.addEventListener("pointerup", Y), window.addEventListener("pointercancel", Q), window.addEventListener("touchmove", X, { passive: !1 }), window.addEventListener("keydown", Z);
|
|
63
64
|
}
|
|
64
65
|
function K() {
|
|
65
66
|
if (!B) return;
|
|
@@ -77,17 +78,17 @@ var te = ["aria-labelledby"], ne = { class: "flex items-center justify-between p
|
|
|
77
78
|
if (B) {
|
|
78
79
|
if (!R.value) {
|
|
79
80
|
let t = Math.hypot(e.clientX - B.x, e.clientY - B.y);
|
|
80
|
-
if (B.touch) return t >
|
|
81
|
-
if (t <
|
|
81
|
+
if (B.touch) return t > k ? Q() : void 0;
|
|
82
|
+
if (t < O) return;
|
|
82
83
|
K();
|
|
83
84
|
}
|
|
84
|
-
e.preventDefault(), J(e.clientX, e.clientY),
|
|
85
|
+
e.preventDefault(), J(e.clientX, e.clientY), ae(e.clientX, e.clientY), oe(e.clientX, e.clientY);
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
function J(e, t) {
|
|
88
89
|
H && (H.style.transform = `translate(${e - U.x}px, ${t - U.y}px) rotate(1.5deg)`);
|
|
89
90
|
}
|
|
90
|
-
function
|
|
91
|
+
function ae(e, t) {
|
|
91
92
|
let n = document.elementFromPoint(e, t), r = n?.closest("[data-board-column]");
|
|
92
93
|
if (!r || !L.value?.contains(r)) {
|
|
93
94
|
z.value = null;
|
|
@@ -103,98 +104,102 @@ var te = ["aria-labelledby"], ne = { class: "flex items-center justify-between p
|
|
|
103
104
|
index: o
|
|
104
105
|
});
|
|
105
106
|
}
|
|
106
|
-
function
|
|
107
|
+
function oe(e, t) {
|
|
107
108
|
let n = document.elementFromPoint(e, t)?.closest("[data-board-column]");
|
|
108
109
|
if (n) {
|
|
109
110
|
let e = n.getBoundingClientRect();
|
|
110
|
-
t < e.top +
|
|
111
|
+
t < e.top + j ? n.scrollTop -= 12 : t > e.bottom - j && (n.scrollTop += 12);
|
|
111
112
|
}
|
|
112
113
|
let r = L.value?.getBoundingClientRect();
|
|
113
|
-
r && L.value && (e < r.left +
|
|
114
|
+
r && L.value && (e < r.left + j ? L.value.scrollLeft -= 12 : e > r.right - j && (L.value.scrollLeft += 12));
|
|
114
115
|
}
|
|
115
|
-
function
|
|
116
|
+
function Y() {
|
|
116
117
|
let e = R.value, t = z.value;
|
|
117
118
|
if (e && t) {
|
|
118
119
|
let n = t.index;
|
|
119
120
|
e.column === t.column ? (n > e.index && n--, N.sortable && n !== e.index && P("move", e.id, t.column, n)) : P("move", e.id, t.column, n);
|
|
120
121
|
}
|
|
121
|
-
|
|
122
|
+
Q();
|
|
122
123
|
}
|
|
123
|
-
function
|
|
124
|
+
function X(e) {
|
|
124
125
|
R.value && e.preventDefault();
|
|
125
126
|
}
|
|
126
|
-
function
|
|
127
|
-
e.key === "Escape" && R.value && (e.preventDefault(),
|
|
127
|
+
function Z(e) {
|
|
128
|
+
e.key === "Escape" && R.value && (e.preventDefault(), Q());
|
|
128
129
|
}
|
|
129
|
-
function
|
|
130
|
-
window.clearTimeout(V), window.removeEventListener("pointermove", q), window.removeEventListener("pointerup",
|
|
130
|
+
function Q() {
|
|
131
|
+
window.clearTimeout(V), window.removeEventListener("pointermove", q), window.removeEventListener("pointerup", Y), window.removeEventListener("pointercancel", Q), window.removeEventListener("touchmove", X), window.removeEventListener("keydown", Z), H?.remove(), H = null, R.value && (W = !0, window.setTimeout(() => W = !1)), B = null, R.value = null, z.value = null;
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
-
function
|
|
133
|
+
f(Q);
|
|
134
|
+
function se(e, t) {
|
|
134
135
|
W ? (e.preventDefault(), e.stopPropagation()) : N.onCardClick?.(t);
|
|
135
136
|
}
|
|
136
|
-
function
|
|
137
|
+
function ce(e, t) {
|
|
137
138
|
!N.onCardClick || e.target !== e.currentTarget || e.key !== "Enter" && e.key !== " " || (e.preventDefault(), N.onCardClick(t));
|
|
138
139
|
}
|
|
139
|
-
let
|
|
140
|
-
|
|
140
|
+
let le = {
|
|
141
|
+
fill: "bg-sui-fill",
|
|
142
|
+
canvas: "border border-sui-line-subtle bg-sui-canvas",
|
|
143
|
+
sunken: "sui-band"
|
|
144
|
+
}, $ = r(() => le[N.columnTone]), ue = (e, t, n) => z.value?.column === e && z.value.index === t && R.value?.id !== I(n);
|
|
145
|
+
return (r, f) => (p(), o("div", {
|
|
141
146
|
ref_key: "root",
|
|
142
147
|
ref: L,
|
|
143
|
-
class:
|
|
144
|
-
}, [(
|
|
145
|
-
key:
|
|
146
|
-
"aria-labelledby": `${
|
|
148
|
+
class: d(["flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']", u.fill && "h-full min-h-0 flex-1"])
|
|
149
|
+
}, [(p(!0), o(n, null, h(u.columns, (m) => (p(), o("section", {
|
|
150
|
+
key: m.id,
|
|
151
|
+
"aria-labelledby": `${y(F)}-${m.id}`,
|
|
147
152
|
class: "flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col"
|
|
148
|
-
}, [
|
|
149
|
-
id: `${
|
|
153
|
+
}, [s("header", b, [(p(), i(_(`h${u.headingLevel}`), {
|
|
154
|
+
id: `${y(F)}-${m.id}`,
|
|
150
155
|
class: "m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink"
|
|
151
156
|
}, {
|
|
152
|
-
default:
|
|
157
|
+
default: te(() => [m.icon ? (p(), i(e, {
|
|
153
158
|
key: 0,
|
|
154
|
-
color:
|
|
155
|
-
name:
|
|
156
|
-
}, null, 8, ["color", "name"])) :
|
|
159
|
+
color: m.iconColor ?? "ink-subtle",
|
|
160
|
+
name: m.icon
|
|
161
|
+
}, null, 8, ["color", "name"])) : a("", !0), c(" " + v(m.name), 1)]),
|
|
157
162
|
_: 2
|
|
158
|
-
}, 1032, ["id"])),
|
|
159
|
-
class:
|
|
160
|
-
"data-board-column":
|
|
161
|
-
}, [
|
|
163
|
+
}, 1032, ["id"])), s("span", x, [g(r.$slots, "count", { column: m }, () => [c(v(m.items.length), 1)])])]), s("div", {
|
|
164
|
+
class: d(["group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl p-2.5 transition-shadow duration-150", [$.value, z.value?.column === m.id && "ring-2 ring-sui-accent-line ring-inset"]]),
|
|
165
|
+
"data-board-column": m.id
|
|
166
|
+
}, [u.loading ? (p(), o(n, { key: 0 }, h(3, (e) => s("div", {
|
|
162
167
|
key: e,
|
|
163
168
|
"aria-hidden": "true",
|
|
164
169
|
class: "rounded-xl border border-sui-edge bg-sui-raised p-4"
|
|
165
|
-
}, [
|
|
170
|
+
}, [l(t, { class: "mb-3" }), l(t, {
|
|
166
171
|
height: "0.75rem",
|
|
167
172
|
width: "70%"
|
|
168
|
-
})])), 64)) :
|
|
169
|
-
class:
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
})])), 64)) : m.items.length === 0 && !R.value ? (p(), o("p", C, v(u.emptyLabel), 1)) : (p(), o("ul", w, [(p(!0), o(n, null, h(m.items, (e, t) => (p(), o("li", { key: I(e) }, [s("div", {
|
|
174
|
+
class: d(["relative rounded-xl outline-hidden transition-opacity duration-150 select-none", [
|
|
175
|
+
u.cardFrame && ie,
|
|
176
|
+
u.onCardClick && "cursor-pointer focus-visible:sui-ring",
|
|
172
177
|
R.value?.id === I(e) && "opacity-40",
|
|
173
|
-
|
|
178
|
+
ue(m.id, t, e) && "shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]"
|
|
174
179
|
]]),
|
|
175
180
|
"data-board-index": t,
|
|
176
|
-
role:
|
|
177
|
-
tabindex:
|
|
178
|
-
onClickCapture: (t) =>
|
|
179
|
-
onContextmenu:
|
|
180
|
-
onDragstart:
|
|
181
|
-
onKeydown: (t) =>
|
|
182
|
-
onPointerdown: (n) => G(n, e,
|
|
183
|
-
}, [
|
|
184
|
-
column:
|
|
181
|
+
role: u.onCardClick ? "button" : void 0,
|
|
182
|
+
tabindex: u.onCardClick ? 0 : void 0,
|
|
183
|
+
onClickCapture: (t) => se(t, e),
|
|
184
|
+
onContextmenu: f[0] ||= (e) => R.value && e.preventDefault(),
|
|
185
|
+
onDragstart: f[1] ||= ne(() => {}, ["prevent"]),
|
|
186
|
+
onKeydown: (t) => ce(t, e),
|
|
187
|
+
onPointerdown: (n) => G(n, e, m.id, t)
|
|
188
|
+
}, [g(r.$slots, "card", {
|
|
189
|
+
column: m,
|
|
185
190
|
item: e
|
|
186
|
-
}, () => [
|
|
191
|
+
}, () => [s("p", E, v(e.name), 1)])], 42, T)]))), 128))])), u.onNew && !u.loading ? (p(), o("button", {
|
|
187
192
|
key: 3,
|
|
188
193
|
class: "flex h-(--sui-control-height) w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring",
|
|
189
194
|
type: "button",
|
|
190
|
-
onClick: (e) =>
|
|
191
|
-
}, [
|
|
195
|
+
onClick: (e) => u.onNew(m)
|
|
196
|
+
}, [f[2] ||= s("svg", {
|
|
192
197
|
"aria-hidden": "true",
|
|
193
198
|
class: "size-3.5 fill-none stroke-current",
|
|
194
199
|
"stroke-linecap": "round",
|
|
195
200
|
"stroke-width": "1.5",
|
|
196
201
|
viewBox: "0 0 14 14"
|
|
197
|
-
}, [
|
|
202
|
+
}, [s("path", { d: "M7 2.5v9M2.5 7h9" })], -1), c(" " + v(u.newLabel), 1)], 8, D)) : a("", !0)], 10, S)], 8, re))), 128))], 2));
|
|
198
203
|
}
|
|
199
204
|
});
|
|
200
205
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Board.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_unref","_createElementVNode","_createBlock","_resolveDynamicComponent","_withCtx","Icon","_createCommentVNode","_createTextVNode","_toDisplayString","_renderSlot","_createVNode","Skeleton","_withModifiers","Icon_default","Skeleton_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","MOUSE_SLOP","TOUCH_SLOP","TOUCH_DELAY","EDGE","frame","Board_vue_vue_type_script_setup_true_lang_default","__emit","props","__props","emit","id","useId","idOf","item","root","ref","dragged","target","pending","pressTimer","ghost","grab","justDragged","onPointerDown","event","touch","start","onPointerMove","onPointerUp","stop","holdScroll","onEscape","rect","follow","moved","aim","scrollNear","x","y","under","body","column","card","index","c","board","from","to","clickCard","onCardKeydown","isTarget","openBlock","createElementBlock","normalizeClass","Fragment","renderList","unref","createElementVNode","createBlock","resolveDynamicComponent","withCtx","createCommentVNode","createTextVNode","toDisplayString","renderSlot","_ctx","i","createVNode","$event","_cache","withModifiers"],"sources":["../../../src/components/Board/Board.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { onBeforeUnmount, ref, useId } from 'vue'\n\nimport Icon from '../Icon/Icon.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { BoardColumn } from './types'\n\nexport interface BoardProps<T> {\n columns: BoardColumn<T>[]\n /** The item field that identifies it. */\n rowKey?: keyof T & string\n /** Cards also reorder within a column, for records that carry a position. */\n sortable?: boolean\n /** Columns run to the bottom of the parent and scroll inside it; give the parent a height. False sizes them to their cards. */\n fill?: boolean\n loading?: boolean\n /** The columns' heading level, to fit the page's outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n /** Draws each card's border, padding and surface. False when the `card` slot draws its own. */\n cardFrame?: boolean\n /** Makes a card a button that opens on click, Enter and Space (`@card-click`). Without it, the card slot carries its\n * own link or buttons. */\n onCardClick?: (item: T) => void\n /** Adds a dashed \"New\" button at the foot of each column, shown on hover (`@new`). */\n onNew?: (column: BoardColumn<T>) => void\n newLabel?: string\n emptyLabel?: string\n}\n\nconst props = withDefaults(defineProps<BoardProps<T>>(), {\n rowKey: 'id' as never,\n fill: true,\n headingLevel: 3,\n cardFrame: true,\n onCardClick: undefined,\n onNew: undefined,\n newLabel: 'New',\n emptyLabel: 'No cards'\n})\n\nconst emit = defineEmits<{\n /** A card dropped into a column, at its new index there. Show it moved at once; put it back if saving fails. */\n move: [id: unknown, column: string, index: number]\n}>()\n\ndefineSlots<{\n /** A card's content; `item.name` as a title by default. */\n card?: (props: { item: T; column: BoardColumn<T> }) => unknown\n /** Replaces a column's count, e.g. its summed deal value. */\n count?: (props: { column: BoardColumn<T> }) => unknown\n}>()\n\nconst id = useId()\nconst idOf = (item: T): unknown => item[props.rowKey ?? 'id']\nconst root = ref<HTMLElement>()\n\nconst dragged = ref<{ id: unknown; column: string; index: number } | null>(null)\nconst target = ref<{ column: string; index: number } | null>(null)\n\n// Dragging runs on pointer events, so it works with a mouse, a pen and a finger. A mouse drags once it moves a few\n// pixels, so a click still clicks; a finger after a short press, so a swipe still scrolls.\nconst MOUSE_SLOP = 4\nconst TOUCH_SLOP = 8\nconst TOUCH_DELAY = 250\nconst EDGE = 48\n\nlet pending: {\n item: T\n column: string\n index: number\n x: number\n y: number\n card: HTMLElement\n touch: boolean\n} | null = null\nlet pressTimer: number | undefined\nlet ghost: HTMLElement | null = null\nlet grab = { x: 0, y: 0 }\nlet justDragged = false\n\nfunction onPointerDown(event: PointerEvent, item: T, column: string, index: number) {\n if (event.button !== 0 || (event.target as HTMLElement).closest('input, textarea, select, [contenteditable]')) return\n const touch = event.pointerType === 'touch'\n pending = { item, column, index, x: event.clientX, y: event.clientY, card: event.currentTarget as HTMLElement, touch }\n if (touch) pressTimer = window.setTimeout(() => pending && start(), TOUCH_DELAY)\n window.addEventListener('pointermove', onPointerMove)\n window.addEventListener('pointerup', onPointerUp)\n window.addEventListener('pointercancel', stop)\n window.addEventListener('touchmove', holdScroll, { passive: false })\n window.addEventListener('keydown', onEscape)\n}\n\nfunction start() {\n if (!pending) return\n const rect = pending.card.getBoundingClientRect()\n grab = { x: pending.x - rect.left, y: pending.y - rect.top }\n ghost = pending.card.cloneNode(true) as HTMLElement\n ghost.removeAttribute('id')\n ghost.setAttribute('aria-hidden', 'true')\n ghost.classList.add('pointer-events-none', 'fixed', 'top-0', 'left-0', 'z-[1300]', 'shadow-lg', 'opacity-95')\n ghost.style.width = `${rect.width}px`\n document.body.appendChild(ghost)\n follow(pending.x, pending.y)\n dragged.value = { id: idOf(pending.item), column: pending.column, index: pending.index }\n}\n\nfunction onPointerMove(event: PointerEvent) {\n if (!pending) return\n if (!dragged.value) {\n const moved = Math.hypot(event.clientX - pending.x, event.clientY - pending.y)\n // A finger that moves before the press is long enough is scrolling.\n if (pending.touch) return moved > TOUCH_SLOP ? stop() : undefined\n if (moved < MOUSE_SLOP) return\n start()\n }\n event.preventDefault()\n follow(event.clientX, event.clientY)\n aim(event.clientX, event.clientY)\n scrollNear(event.clientX, event.clientY)\n}\n\nfunction follow(x: number, y: number) {\n if (ghost) ghost.style.transform = `translate(${x - grab.x}px, ${y - grab.y}px) rotate(1.5deg)`\n}\n\n// What's under the pointer: a card's top half drops before it, the bottom half after; an empty spot, at the end.\nfunction aim(x: number, y: number) {\n const under = document.elementFromPoint(x, y)\n const body = under?.closest<HTMLElement>('[data-board-column]')\n if (!body || !root.value?.contains(body)) {\n target.value = null\n return\n }\n const column = body.dataset.boardColumn!\n const card = under!.closest<HTMLElement>('[data-board-index]')\n let index = props.columns.find((c) => c.id === column)?.items.length ?? 0\n if (card && body.contains(card)) {\n const rect = card.getBoundingClientRect()\n index = Number(card.dataset.boardIndex) + (y < rect.top + rect.height / 2 ? 0 : 1)\n }\n if (target.value?.column !== column || target.value.index !== index) target.value = { column, index }\n}\n\n// Near a column's top or bottom, or the board's sides, it scrolls that way.\nfunction scrollNear(x: number, y: number) {\n const body = document.elementFromPoint(x, y)?.closest<HTMLElement>('[data-board-column]')\n if (body) {\n const rect = body.getBoundingClientRect()\n if (y < rect.top + EDGE) body.scrollTop -= 12\n else if (y > rect.bottom - EDGE) body.scrollTop += 12\n }\n const board = root.value?.getBoundingClientRect()\n if (board && root.value) {\n if (x < board.left + EDGE) root.value.scrollLeft -= 12\n else if (x > board.right - EDGE) root.value.scrollLeft += 12\n }\n}\n\nfunction onPointerUp() {\n const from = dragged.value\n const to = target.value\n if (from && to) {\n let index = to.index\n if (from.column === to.column) {\n // Within a column the dragged card leaves its old slot first.\n if (index > from.index) index--\n if (props.sortable && index !== from.index) emit('move', from.id, to.column, index)\n } else {\n emit('move', from.id, to.column, index)\n }\n }\n stop()\n}\n\nfunction holdScroll(event: TouchEvent) {\n if (dragged.value) event.preventDefault()\n}\n\nfunction onEscape(event: KeyboardEvent) {\n if (event.key === 'Escape' && dragged.value) {\n event.preventDefault()\n stop()\n }\n}\n\nfunction stop() {\n window.clearTimeout(pressTimer)\n window.removeEventListener('pointermove', onPointerMove)\n window.removeEventListener('pointerup', onPointerUp)\n window.removeEventListener('pointercancel', stop)\n window.removeEventListener('touchmove', holdScroll)\n window.removeEventListener('keydown', onEscape)\n ghost?.remove()\n ghost = null\n // The click that ends a drag isn't a click on the card.\n if (dragged.value) {\n justDragged = true\n window.setTimeout(() => (justDragged = false))\n }\n pending = null\n dragged.value = null\n target.value = null\n}\n\nonBeforeUnmount(stop)\n\nfunction clickCard(event: MouseEvent, item: T) {\n if (justDragged) {\n event.preventDefault()\n event.stopPropagation()\n return\n }\n props.onCardClick?.(item)\n}\n\nfunction onCardKeydown(event: KeyboardEvent, item: T) {\n if (!props.onCardClick || event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return\n event.preventDefault()\n props.onCardClick(item)\n}\n\nconst isTarget = (column: string, index: number, item: T) =>\n target.value?.column === column && target.value.index === index && dragged.value?.id !== idOf(item)\n\nconst frame =\n 'border border-sui-edge bg-sui-raised p-4 transition-[border-color,opacity] duration-150 hover:border-sui-line-strong'\n</script>\n\n<!-- A board: cards in columns by a field (a stage, a status), dragged between columns. -->\n<template>\n <div\n ref=\"root\"\n class=\"flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']\"\n :class=\"fill && 'h-full min-h-0 flex-1'\"\n >\n <section\n v-for=\"column in columns\"\n :key=\"column.id\"\n :aria-labelledby=\"`${id}-${column.id}`\"\n class=\"flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col\"\n >\n <header class=\"flex items-center justify-between px-1 pb-2.5\">\n <component\n :is=\"`h${headingLevel}`\"\n :id=\"`${id}-${column.id}`\"\n class=\"m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink\"\n >\n <Icon v-if=\"column.icon\" :color=\"column.iconColor ?? 'ink-subtle'\" :name=\"column.icon\" />\n {{ column.name }}\n </component>\n <span\n class=\"ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-fill px-2 text-xs text-sui-ink-muted tabular-nums\"\n >\n <slot :column=\"column\" name=\"count\">{{ column.items.length }}</slot>\n </span>\n </header>\n <div\n class=\"group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl bg-sui-fill p-2.5 transition-shadow duration-150\"\n :class=\"target?.column === column.id && 'ring-2 ring-sui-accent-line ring-inset'\"\n :data-board-column=\"column.id\"\n >\n <template v-if=\"loading\">\n <div v-for=\"i in 3\" :key=\"i\" aria-hidden=\"true\" class=\"rounded-xl border border-sui-edge bg-sui-raised p-4\">\n <Skeleton class=\"mb-3\" />\n <Skeleton height=\"0.75rem\" width=\"70%\" />\n </div>\n </template>\n <p\n v-else-if=\"column.items.length === 0 && !dragged\"\n class=\"m-0 px-1 py-3 text-center text-13 text-sui-ink-muted\"\n >\n {{ emptyLabel }}\n </p>\n <ul v-else class=\"m-0 flex list-none flex-col gap-2.5 p-0\">\n <li v-for=\"(item, index) in column.items\" :key=\"idOf(item) as PropertyKey\">\n <div\n class=\"relative rounded-xl outline-hidden transition-opacity duration-150 select-none\"\n :class=\"[\n cardFrame && frame,\n onCardClick && 'cursor-pointer focus-visible:sui-ring',\n dragged?.id === idOf(item) && 'opacity-40',\n isTarget(column.id, index, item) && 'shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]'\n ]\"\n :data-board-index=\"index\"\n :role=\"onCardClick ? 'button' : undefined\"\n :tabindex=\"onCardClick ? 0 : undefined\"\n @click.capture=\"clickCard($event, item)\"\n @contextmenu=\"dragged && $event.preventDefault()\"\n @dragstart.prevent\n @keydown=\"onCardKeydown($event, item)\"\n @pointerdown=\"onPointerDown($event, item, column.id, index)\"\n >\n <slot :column=\"column\" :item=\"item\" name=\"card\">\n <p class=\"m-0 text-sm font-semibold text-sui-ink\">{{ item.name }}</p>\n </slot>\n </div>\n </li>\n </ul>\n <button\n v-if=\"onNew && !loading\"\n class=\"flex h-(--sui-control-height) w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring\"\n type=\"button\"\n @click=\"onNew(column)\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 14 14\"\n >\n <path d=\"M7 2.5v9M2.5 7h9\" />\n </svg>\n {{ newLabel }}\n </button>\n </div>\n </section>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Board.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_unref","_createElementVNode","_createBlock","_resolveDynamicComponent","_withCtx","Icon","_createCommentVNode","_createTextVNode","_toDisplayString","_renderSlot","_createVNode","Skeleton","_withModifiers","Icon_default","Skeleton_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","MOUSE_SLOP","TOUCH_SLOP","TOUCH_DELAY","EDGE","frame","Board_vue_vue_type_script_setup_true_lang_default","__emit","props","__props","emit","id","useId","idOf","item","root","ref","dragged","target","pending","pressTimer","ghost","grab","justDragged","onPointerDown","event","touch","start","onPointerMove","onPointerUp","stop","holdScroll","onEscape","rect","follow","moved","aim","scrollNear","x","y","under","body","column","card","index","c","board","from","to","clickCard","onCardKeydown","grounds","columnGround","computed","isTarget","openBlock","createElementBlock","normalizeClass","Fragment","renderList","unref","createElementVNode","createBlock","resolveDynamicComponent","withCtx","createCommentVNode","createTextVNode","toDisplayString","renderSlot","_ctx","i","createVNode","$event","_cache","withModifiers"],"sources":["../../../src/components/Board/Board.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { computed, onBeforeUnmount, ref, useId } from 'vue'\n\nimport Icon from '../Icon/Icon.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { BoardColumn } from './types'\n\nexport interface BoardProps<T> {\n columns: BoardColumn<T>[]\n /** The item field that identifies it. */\n rowKey?: keyof T & string\n /** Cards also reorder within a column, for records that carry a position. */\n sortable?: boolean\n /** Columns run to the bottom of the parent and scroll inside it; give the parent a height. False sizes them to their cards. */\n fill?: boolean\n loading?: boolean\n /** The columns' heading level, to fit the page's outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n /** Draws each card's border, padding and surface. False when the `card` slot draws its own. */\n cardFrame?: boolean\n /** Makes a card a button that opens on click, Enter and Space (`@card-click`). Without it, the card slot carries its\n * own link or buttons. */\n onCardClick?: (item: T) => void\n /** Adds a dashed \"New\" button at the foot of each column, shown on hover (`@new`). */\n onNew?: (column: BoardColumn<T>) => void\n newLabel?: string\n emptyLabel?: string\n /** The columns' ground: a soft fill by default; `canvas` (the page's, with a hairline) or `sunken` match a Table's or\n * DataList's header tone, so the cards stand out on it. */\n columnTone?: 'fill' | 'canvas' | 'sunken'\n}\n\nconst props = withDefaults(defineProps<BoardProps<T>>(), {\n rowKey: 'id' as never,\n fill: true,\n headingLevel: 3,\n cardFrame: true,\n onCardClick: undefined,\n onNew: undefined,\n newLabel: 'New',\n emptyLabel: 'No cards',\n columnTone: 'fill'\n})\n\nconst emit = defineEmits<{\n /** A card dropped into a column, at its new index there. Show it moved at once; put it back if saving fails. */\n move: [id: unknown, column: string, index: number]\n}>()\n\ndefineSlots<{\n /** A card's content; `item.name` as a title by default. */\n card?: (props: { item: T; column: BoardColumn<T> }) => unknown\n /** Replaces a column's count, e.g. its summed deal value. */\n count?: (props: { column: BoardColumn<T> }) => unknown\n}>()\n\nconst id = useId()\nconst idOf = (item: T): unknown => item[props.rowKey ?? 'id']\nconst root = ref<HTMLElement>()\n\nconst dragged = ref<{ id: unknown; column: string; index: number } | null>(null)\nconst target = ref<{ column: string; index: number } | null>(null)\n\n// Dragging runs on pointer events, so it works with a mouse, a pen and a finger. A mouse drags once it moves a few\n// pixels, so a click still clicks; a finger after a short press, so a swipe still scrolls.\nconst MOUSE_SLOP = 4\nconst TOUCH_SLOP = 8\nconst TOUCH_DELAY = 250\nconst EDGE = 48\n\nlet pending: {\n item: T\n column: string\n index: number\n x: number\n y: number\n card: HTMLElement\n touch: boolean\n} | null = null\nlet pressTimer: number | undefined\nlet ghost: HTMLElement | null = null\nlet grab = { x: 0, y: 0 }\nlet justDragged = false\n\nfunction onPointerDown(event: PointerEvent, item: T, column: string, index: number) {\n if (event.button !== 0 || (event.target as HTMLElement).closest('input, textarea, select, [contenteditable]')) return\n const touch = event.pointerType === 'touch'\n pending = { item, column, index, x: event.clientX, y: event.clientY, card: event.currentTarget as HTMLElement, touch }\n if (touch) pressTimer = window.setTimeout(() => pending && start(), TOUCH_DELAY)\n window.addEventListener('pointermove', onPointerMove)\n window.addEventListener('pointerup', onPointerUp)\n window.addEventListener('pointercancel', stop)\n window.addEventListener('touchmove', holdScroll, { passive: false })\n window.addEventListener('keydown', onEscape)\n}\n\nfunction start() {\n if (!pending) return\n const rect = pending.card.getBoundingClientRect()\n grab = { x: pending.x - rect.left, y: pending.y - rect.top }\n ghost = pending.card.cloneNode(true) as HTMLElement\n ghost.removeAttribute('id')\n ghost.setAttribute('aria-hidden', 'true')\n ghost.classList.add('pointer-events-none', 'fixed', 'top-0', 'left-0', 'z-[1300]', 'shadow-lg', 'opacity-95')\n ghost.style.width = `${rect.width}px`\n document.body.appendChild(ghost)\n follow(pending.x, pending.y)\n dragged.value = { id: idOf(pending.item), column: pending.column, index: pending.index }\n}\n\nfunction onPointerMove(event: PointerEvent) {\n if (!pending) return\n if (!dragged.value) {\n const moved = Math.hypot(event.clientX - pending.x, event.clientY - pending.y)\n // A finger that moves before the press is long enough is scrolling.\n if (pending.touch) return moved > TOUCH_SLOP ? stop() : undefined\n if (moved < MOUSE_SLOP) return\n start()\n }\n event.preventDefault()\n follow(event.clientX, event.clientY)\n aim(event.clientX, event.clientY)\n scrollNear(event.clientX, event.clientY)\n}\n\nfunction follow(x: number, y: number) {\n if (ghost) ghost.style.transform = `translate(${x - grab.x}px, ${y - grab.y}px) rotate(1.5deg)`\n}\n\n// What's under the pointer: a card's top half drops before it, the bottom half after; an empty spot, at the end.\nfunction aim(x: number, y: number) {\n const under = document.elementFromPoint(x, y)\n const body = under?.closest<HTMLElement>('[data-board-column]')\n if (!body || !root.value?.contains(body)) {\n target.value = null\n return\n }\n const column = body.dataset.boardColumn!\n const card = under!.closest<HTMLElement>('[data-board-index]')\n let index = props.columns.find((c) => c.id === column)?.items.length ?? 0\n if (card && body.contains(card)) {\n const rect = card.getBoundingClientRect()\n index = Number(card.dataset.boardIndex) + (y < rect.top + rect.height / 2 ? 0 : 1)\n }\n if (target.value?.column !== column || target.value.index !== index) target.value = { column, index }\n}\n\n// Near a column's top or bottom, or the board's sides, it scrolls that way.\nfunction scrollNear(x: number, y: number) {\n const body = document.elementFromPoint(x, y)?.closest<HTMLElement>('[data-board-column]')\n if (body) {\n const rect = body.getBoundingClientRect()\n if (y < rect.top + EDGE) body.scrollTop -= 12\n else if (y > rect.bottom - EDGE) body.scrollTop += 12\n }\n const board = root.value?.getBoundingClientRect()\n if (board && root.value) {\n if (x < board.left + EDGE) root.value.scrollLeft -= 12\n else if (x > board.right - EDGE) root.value.scrollLeft += 12\n }\n}\n\nfunction onPointerUp() {\n const from = dragged.value\n const to = target.value\n if (from && to) {\n let index = to.index\n if (from.column === to.column) {\n // Within a column the dragged card leaves its old slot first.\n if (index > from.index) index--\n if (props.sortable && index !== from.index) emit('move', from.id, to.column, index)\n } else {\n emit('move', from.id, to.column, index)\n }\n }\n stop()\n}\n\nfunction holdScroll(event: TouchEvent) {\n if (dragged.value) event.preventDefault()\n}\n\nfunction onEscape(event: KeyboardEvent) {\n if (event.key === 'Escape' && dragged.value) {\n event.preventDefault()\n stop()\n }\n}\n\nfunction stop() {\n window.clearTimeout(pressTimer)\n window.removeEventListener('pointermove', onPointerMove)\n window.removeEventListener('pointerup', onPointerUp)\n window.removeEventListener('pointercancel', stop)\n window.removeEventListener('touchmove', holdScroll)\n window.removeEventListener('keydown', onEscape)\n ghost?.remove()\n ghost = null\n // The click that ends a drag isn't a click on the card.\n if (dragged.value) {\n justDragged = true\n window.setTimeout(() => (justDragged = false))\n }\n pending = null\n dragged.value = null\n target.value = null\n}\n\nonBeforeUnmount(stop)\n\nfunction clickCard(event: MouseEvent, item: T) {\n if (justDragged) {\n event.preventDefault()\n event.stopPropagation()\n return\n }\n props.onCardClick?.(item)\n}\n\nfunction onCardKeydown(event: KeyboardEvent, item: T) {\n if (!props.onCardClick || event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return\n event.preventDefault()\n props.onCardClick(item)\n}\n\nconst grounds = { fill: 'bg-sui-fill', canvas: 'border border-sui-line-subtle bg-sui-canvas', sunken: 'sui-band' }\nconst columnGround = computed(() => grounds[props.columnTone])\n\nconst isTarget = (column: string, index: number, item: T) =>\n target.value?.column === column && target.value.index === index && dragged.value?.id !== idOf(item)\n\nconst frame =\n 'border border-sui-edge bg-sui-raised p-4 transition-[border-color,opacity] duration-150 hover:border-sui-line-strong'\n</script>\n\n<!-- A board: cards in columns by a field (a stage, a status), dragged between columns. -->\n<template>\n <div\n ref=\"root\"\n class=\"flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']\"\n :class=\"fill && 'h-full min-h-0 flex-1'\"\n >\n <section\n v-for=\"column in columns\"\n :key=\"column.id\"\n :aria-labelledby=\"`${id}-${column.id}`\"\n class=\"flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col\"\n >\n <header class=\"flex items-center justify-between px-1 pb-2.5\">\n <component\n :is=\"`h${headingLevel}`\"\n :id=\"`${id}-${column.id}`\"\n class=\"m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink\"\n >\n <Icon v-if=\"column.icon\" :color=\"column.iconColor ?? 'ink-subtle'\" :name=\"column.icon\" />\n {{ column.name }}\n </component>\n <span\n class=\"ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-fill px-2 text-xs text-sui-ink-muted tabular-nums\"\n >\n <slot :column=\"column\" name=\"count\">{{ column.items.length }}</slot>\n </span>\n </header>\n <div\n class=\"group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl p-2.5 transition-shadow duration-150\"\n :class=\"[columnGround, target?.column === column.id && 'ring-2 ring-sui-accent-line ring-inset']\"\n :data-board-column=\"column.id\"\n >\n <template v-if=\"loading\">\n <div v-for=\"i in 3\" :key=\"i\" aria-hidden=\"true\" class=\"rounded-xl border border-sui-edge bg-sui-raised p-4\">\n <Skeleton class=\"mb-3\" />\n <Skeleton height=\"0.75rem\" width=\"70%\" />\n </div>\n </template>\n <p\n v-else-if=\"column.items.length === 0 && !dragged\"\n class=\"m-0 px-1 py-3 text-center text-13 text-sui-ink-muted\"\n >\n {{ emptyLabel }}\n </p>\n <ul v-else class=\"m-0 flex list-none flex-col gap-2.5 p-0\">\n <li v-for=\"(item, index) in column.items\" :key=\"idOf(item) as PropertyKey\">\n <div\n class=\"relative rounded-xl outline-hidden transition-opacity duration-150 select-none\"\n :class=\"[\n cardFrame && frame,\n onCardClick && 'cursor-pointer focus-visible:sui-ring',\n dragged?.id === idOf(item) && 'opacity-40',\n isTarget(column.id, index, item) && 'shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]'\n ]\"\n :data-board-index=\"index\"\n :role=\"onCardClick ? 'button' : undefined\"\n :tabindex=\"onCardClick ? 0 : undefined\"\n @click.capture=\"clickCard($event, item)\"\n @contextmenu=\"dragged && $event.preventDefault()\"\n @dragstart.prevent\n @keydown=\"onCardKeydown($event, item)\"\n @pointerdown=\"onPointerDown($event, item, column.id, index)\"\n >\n <slot :column=\"column\" :item=\"item\" name=\"card\">\n <p class=\"m-0 text-sm font-semibold text-sui-ink\">{{ item.name }}</p>\n </slot>\n </div>\n </li>\n </ul>\n <button\n v-if=\"onNew && !loading\"\n class=\"flex h-(--sui-control-height) w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring\"\n type=\"button\"\n @click=\"onNew(column)\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 14 14\"\n >\n <path d=\"M7 2.5v9M2.5 7h9\" />\n </svg>\n {{ newLabel }}\n </button>\n </div>\n </section>\n </div>\n</template>\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/styles/tailwind.css
CHANGED
|
@@ -370,6 +370,25 @@
|
|
|
370
370
|
--sui-focus-offset-inset: -2px;
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
/* A clickable card's hover ground: the soft fill mixed into surface, opaque so it can fade in (a gradient can't).
|
|
374
|
+
On every [data-mode] element, so a subtree in the other mode works it out again. */
|
|
375
|
+
:where(:root, [data-mode]) {
|
|
376
|
+
--sui-card-hover: color-mix(in srgb, rgb(var(--sui-surface)), rgb(var(--sui-fill)) calc(var(--sui-fill-alpha) * 60%));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* A clickable card on surface (a board card, a choice, a record tile): on hover its border firms up to `line` and its
|
|
380
|
+
ground fades into --sui-card-hover. Pair it with a `line-subtle` (or `line`) border and a surface ground. */
|
|
381
|
+
@utility sui-card-hover {
|
|
382
|
+
transition:
|
|
383
|
+
border-color 150ms,
|
|
384
|
+
background-color 150ms;
|
|
385
|
+
|
|
386
|
+
&:hover {
|
|
387
|
+
border-color: rgb(var(--sui-line));
|
|
388
|
+
background-color: var(--sui-card-hover);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
373
392
|
/* The soft fill as an opaque band over surface, for what content scrolls under or what overlaps (sticky headers,
|
|
374
393
|
stacked avatars). */
|
|
375
394
|
@utility sui-band {
|