Both advertised behaviour they did not have. LayoutSplitter declared resizeStart, resize and resizeEnd with no pointer handling whatsoever, so a caller wired up @resize and received nothing, for ever, with no error, and its props were columns, gap and maxWidth copied from a grid scaffold. CustomScrollbar was the same shape with a scroll output. The splitter now resizes. Dragging lives in the reactive runtime behind data-wrn-splitter, because a pointermove fires far too often to route through a client function and a state write made in that callback is dropped; the resolved size is held on the container as a --wrn-split custom property and the component grids from it. The handle is a real separator: arrow keys step it, Home and End go to the bounds rather than to nothing, and it carries aria-valuenow, aria-valuemin and aria-valuemax. minSize fixes both bounds so neither pane can be dragged away and left unrecoverable. CustomScrollbar is CSS rather than script -- scrollbar-width and scrollbar-color with webkit rules for the engines that still need them -- and its fake scroll output is removed rather than left unimplemented, since a caller can listen for a plain scroll event. The test harness needed a fix too: mount did not bind the window CustomEvent, so the runtime built events from the host global and happy-dom listeners never matched them, which made anything dispatched look silently lost. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
123 lines
3.5 KiB
Plaintext
123 lines
3.5 KiB
Plaintext
// CustomScrollbar -- a scrolling region with a themed scrollbar.
|
|
//
|
|
// <CustomScrollbar maxHeight="20rem">...long content...</CustomScrollbar>
|
|
//
|
|
// Scrollbar appearance is CSS, not script: scrollbar-width and scrollbar-color
|
|
// are the standard properties, and the ::-webkit-scrollbar rules cover the
|
|
// browsers that still need them.
|
|
//
|
|
// This component used to declare a scroll output it never emitted, and its
|
|
// props were columns, gap and maxWidth copied from a grid scaffold. The output
|
|
// is removed rather than left unimplemented -- a caller can listen for a plain
|
|
// scroll event on the element, which is what it would have been anyway.
|
|
//
|
|
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
|
// and silently swallows the rule that follows it.
|
|
component CustomScrollbar {
|
|
props {
|
|
color: string = "primary"
|
|
size: string = "default"
|
|
axis: string = "vertical"
|
|
// Track thickness in pixels. Clamped, because it arrives as an attribute
|
|
// and a scrollbar wider than the content is not useful to anyone.
|
|
thickness: number = 8
|
|
maxHeight: string = "20rem"
|
|
radius: string = "999px"
|
|
class: string = ""
|
|
}
|
|
|
|
functions {
|
|
shared function trackSize() {
|
|
var value = Number(thickness)
|
|
if (!value || value < 2) {
|
|
return 8
|
|
}
|
|
return Math.min(24, value)
|
|
}
|
|
}
|
|
|
|
view {
|
|
<div
|
|
{...attrs}
|
|
data-ui-component="CustomScrollbar"
|
|
class='wire-scrollbar {class}'
|
|
data-color='{color}'
|
|
data-size='{size}'
|
|
data-axis='{axis}'
|
|
style='--scrollbar-thickness:{trackSize()}px;--scrollbar-radius:{radius};max-height:{maxHeight};'
|
|
>
|
|
<slot />
|
|
</div>
|
|
}
|
|
|
|
style {
|
|
.wire-scrollbar {
|
|
--scrollbar-accent: var(--wire-color-primary);
|
|
min-width: 0;
|
|
overflow: auto;
|
|
/*
|
|
* The standard properties. Firefox and current Chrome honour these; the
|
|
* webkit rules below are only for the engines that still ignore them.
|
|
*/
|
|
scrollbar-width: thin;
|
|
scrollbar-color: color-mix(in srgb, var(--scrollbar-accent) 55%, transparent) transparent;
|
|
overscroll-behavior: contain;
|
|
}
|
|
|
|
.wire-scrollbar[data-color="secondary"] {
|
|
--scrollbar-accent: var(--wire-color-secondary);
|
|
}
|
|
|
|
.wire-scrollbar[data-color="success"] {
|
|
--scrollbar-accent: var(--wire-color-success);
|
|
}
|
|
|
|
.wire-scrollbar[data-color="danger"] {
|
|
--scrollbar-accent: var(--wire-color-danger);
|
|
}
|
|
|
|
.wire-scrollbar[data-color="info"] {
|
|
--scrollbar-accent: var(--wire-color-info);
|
|
}
|
|
|
|
.wire-scrollbar[data-axis="vertical"] {
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.wire-scrollbar[data-axis="horizontal"] {
|
|
overflow-x: auto;
|
|
overflow-y: hidden;
|
|
}
|
|
|
|
.wire-scrollbar::-webkit-scrollbar {
|
|
width: var(--scrollbar-thickness, 8px);
|
|
height: var(--scrollbar-thickness, 8px);
|
|
}
|
|
|
|
.wire-scrollbar::-webkit-scrollbar-track {
|
|
background: var(--wire-color-surface-soft);
|
|
border-radius: var(--scrollbar-radius, 999px);
|
|
}
|
|
|
|
.wire-scrollbar::-webkit-scrollbar-thumb {
|
|
background: color-mix(in srgb, var(--scrollbar-accent) 45%, transparent);
|
|
border-radius: var(--scrollbar-radius, 999px);
|
|
}
|
|
|
|
.wire-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
background: var(--scrollbar-accent);
|
|
}
|
|
|
|
/*
|
|
* A pointer-less device paints its own overlay scrollbar and ignores the
|
|
* width above, so the region keeps its own padding instead.
|
|
*/
|
|
@media (hover: none) {
|
|
.wire-scrollbar {
|
|
scrollbar-width: auto;
|
|
}
|
|
}
|
|
}
|
|
}
|