@ds-mo/ui 1.5.0 → 1.6.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/README.md +1 -1
- package/dist/.build-stamp +1 -1
- package/dist/components/ds-banner.js +1 -1
- package/dist/components/ds-banner.js.map +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-button-group.js +1 -1
- package/dist/components/ds-button.js +1 -1
- package/dist/components/ds-loader.js +1 -1
- package/dist/components/ds-menu.js +1 -1
- package/dist/components/ds-modal.js +1 -1
- package/dist/components/ds-modal.js.map +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-nav.js.map +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-toast-provider.js +1 -1
- package/dist/components/ds-toast-provider.js.map +1 -1
- package/dist/components/ds-toggle-button-group.js +1 -1
- package/dist/components/ds-toggle-button.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/ds-tooltip.js.map +1 -1
- package/dist/components/p-BRju0sEC.js +2 -0
- package/dist/components/p-BRju0sEC.js.map +1 -0
- package/dist/components/p-BvmooLHV.js +2 -0
- package/dist/components/p-BvmooLHV.js.map +1 -0
- package/dist/components/p-C4WbCXBj.js +2 -0
- package/dist/components/p-C4WbCXBj.js.map +1 -0
- package/dist/components/p-CYJ-de0u.js +2 -0
- package/dist/components/p-CYJ-de0u.js.map +1 -0
- package/dist/components/{p--YDw8Q4o.js → p-CsO880L-.js} +2 -2
- package/dist/components/p-DTjpgYJV.js +2 -0
- package/dist/components/p-DTjpgYJV.js.map +1 -0
- package/dist/components/p-DfL2MOso.js +2 -0
- package/dist/components/p-DfL2MOso.js.map +1 -0
- package/dist/components/p-DiWQFROF.js +2 -0
- package/dist/components/p-DiWQFROF.js.map +1 -0
- package/dist/types/components/Banner/Banner.d.ts +2 -0
- package/dist/types/components/Loader/Loader.d.ts +3 -2
- package/dist/types/components/Menu/Menu.d.ts +11 -2
- package/dist/types/components/Modal/Modal.d.ts +1 -0
- package/dist/types/components/PanelNav/PanelNav.d.ts +3 -6
- package/dist/types/components/Toast/Toast.d.ts +1 -0
- package/dist/types/components/Tooltip/Tooltip.d.ts +14 -3
- package/dist/types/components.d.ts +42 -103
- package/dist/types/nav/shell-view-transition.d.ts +2 -2
- package/dist/types/utils/index.d.ts +5 -0
- package/dist/types/utils/resolve-css-length-px.d.ts +8 -0
- package/dist/types/utils/resolve-css-time-ms.d.ts +8 -0
- package/dist/types/utils/token-defaults.d.ts +27 -0
- package/package.json +2 -2
- package/src/angular/index.ts +0 -1
- package/src/angular/proxies.ts +0 -33
- package/src/react/components.ts +0 -1
- package/src/wc/components/Banner/Banner.tsx +11 -5
- package/src/wc/components/Loader/Loader.tsx +8 -3
- package/src/wc/components/Menu/Menu.tsx +61 -26
- package/src/wc/components/Modal/Modal.tsx +10 -7
- package/src/wc/components/PanelNav/PanelNav.tsx +5 -8
- package/src/wc/components/Toast/Toast.tsx +7 -2
- package/src/wc/components/Toast/toast-service.ts +8 -1
- package/src/wc/components/ToggleButton/ToggleButton.tsx +7 -3
- package/src/wc/components/Tooltip/Tooltip.tsx +70 -31
- package/src/wc/components.d.ts +42 -103
- package/src/wc/nav/shell-view-transition.ts +4 -10
- package/dist/components/ds-sidebar.d.ts +0 -11
- package/dist/components/ds-sidebar.js +0 -2
- package/dist/components/ds-sidebar.js.map +0 -1
- package/dist/components/p-BcuUR5Ao.js +0 -2
- package/dist/components/p-BcuUR5Ao.js.map +0 -1
- package/dist/components/p-CpbnmEu1.js +0 -2
- package/dist/components/p-CpbnmEu1.js.map +0 -1
- package/dist/components/p-DznbYg1m.js +0 -2
- package/dist/components/p-DznbYg1m.js.map +0 -1
- package/dist/components/p-uMN1Ppw2.js +0 -2
- package/dist/components/p-uMN1Ppw2.js.map +0 -1
- package/dist/types/components/Sidebar/Sidebar.d.ts +0 -29
- package/src/react/ds-sidebar.ts +0 -33
- package/src/wc/components/Sidebar/Sidebar.tsx +0 -245
- /package/dist/components/{p--YDw8Q4o.js.map → p-CsO880L-.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["toggleButtonCss","ICON_SIZE","md","sm","xs","ICON_PX","TEXT_CLASS","ToggleButton","__stencil_proxyCustomElement","HTMLElement","constructor","registerHost","this","elevation","size","rounded","pressed","inactive","handleClick","dsChange","emit","render","iconSize","iconPx","textClass","hasIcon","icon","hasLabel","label","isIconOnly","isLabelOnly","isIconAndLabel","bg","background","elevKey","charAt","toUpperCase","slice","cls","sizeSM","sizeXS","iconOnly","labelOnly","iconAndLabel","onMedium","onBold","onStrong","onAlwaysDark","h","Host","key","type","class","disabled","ariaLabel","onClick","style","fontSize","name"],"sources":["src/wc/components/ToggleButton/ToggleButton.css?tag=ds-toggle-button&encapsulation=scoped","src/wc/components/ToggleButton/ToggleButton.tsx"],"sourcesContent":["/* ==========================================================================\n ToggleButton — hover/pressed via direct background-color + color-mix().\n Elevation controls the chrome level:\n elevated — bg-primary + shadow (default)\n flat — border only\n none — ghost (transparent, no border, no shadow)\n floating — bg-primary + FAB-strength shadow\n ========================================================================== */\n\n.toggle-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: var(--dimension-radius-050);\n cursor: pointer;\n position: relative;\n transition: transform var(--effect-motion-short-2);\n height: var(--dimension-size-400);\n font-family: inherit;\n background: transparent;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n\n /* Interaction — ghost defaults */\n --_hover-bg: var(--color-interaction-hover);\n --_pressed-bg: var(--color-interaction-pressed);\n --_border: var(--color-border-tertiary);\n}\n\n.toggle-btn:focus-visible {\n outline: var(--dimension-stroke-width-025) solid var(--color-interaction-focus);\n outline-offset: var(--dimension-space-025);\n}\n\n.toggle-btn:hover:not(.inactive) {\n background-color: var(--_hover-bg);\n}\n\n.toggle-btn:active:not(.inactive) {\n background-color: var(--_pressed-bg);\n transform: scale(var(--dimension-scale-subtle));\n}\n\n/* ==========================================================================\n Background context — parent surface awareness\n ========================================================================== */\n\n.onMedium {\n --_hover-bg: var(--color-interaction-on-medium-background-hover);\n --_pressed-bg: var(--color-interaction-on-medium-background-pressed);\n --_border: var(--color-border-on-medium-background-tertiary);\n}\n\n.onBold {\n --_hover-bg: var(--color-interaction-on-bold-background-hover);\n --_pressed-bg: var(--color-interaction-on-bold-background-pressed);\n --_border: var(--color-border-on-bold-background-tertiary);\n}\n\n.onStrong {\n --_hover-bg: var(--color-interaction-on-strong-background-hover);\n --_pressed-bg: var(--color-interaction-on-strong-background-pressed);\n --_border: var(--color-border-on-strong-background-tertiary);\n}\n\n.onAlwaysDark {\n --_hover-bg: var(--color-always-dark-interaction-hover);\n --_pressed-bg: var(--color-always-dark-interaction-pressed);\n --_border: var(--color-always-dark-border-tertiary);\n}\n\n/* ==========================================================================\n Elevation levels\n ========================================================================== */\n\n/* ── Elevated (default): bg-primary + shadow ───────────────────────────── */\n\n.elevationElevated {\n background-color: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-sm);\n color: var(--color-foreground-secondary);\n --_hover-bg: color-mix(in oklch, var(--color-background-primary) 90%, var(--color-foreground-primary));\n --_pressed-bg: color-mix(in oklch, var(--color-background-primary) 85%, var(--color-foreground-primary));\n}\n\n/* ── Flat: border only, transparent bg ─────────────────────────────────── */\n\n.elevationFlat {\n box-shadow: inset 0 0 0 1px var(--_border);\n color: var(--color-foreground-secondary);\n}\n\n/* ── None: ghost — transparent, no border, no shadow ───────────────────── */\n\n.elevationNone {\n color: var(--color-foreground-secondary);\n}\n\n/* ── Floating: bg-primary + FAB-strength shadow ─────────────────────────── */\n\n.elevationFloating {\n background-color: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-floating);\n color: var(--color-foreground-secondary);\n --_hover-bg: color-mix(in oklch, var(--color-background-primary) 90%, var(--color-foreground-primary));\n --_pressed-bg: color-mix(in oklch, var(--color-background-primary) 85%, var(--color-foreground-primary));\n}\n\n/* ── Fg on bold / strong surface ────────────────────────────────────────── */\n\n.onBold.elevationNone, .onBold.elevationFlat,\n.onStrong.elevationNone, .onStrong.elevationFlat {\n color: var(--color-foreground-on-bold-background-secondary);\n}\n\n/* ── Fg on always-dark surface ─────────────────────────────────────────── */\n\n.onAlwaysDark.elevationNone, .onAlwaysDark.elevationFlat {\n color: var(--color-always-dark-foreground-secondary);\n}\n\n/* ==========================================================================\n Pressed state — shared across all elevation levels\n ========================================================================== */\n\n.pressed {\n background-color: var(--color-background-faint-brand);\n color: var(--color-foreground-bold-brand);\n}\n\n/* ==========================================================================\n Layout\n ========================================================================== */\n\n.iconOnly { padding: 0; width: var(--dimension-size-400); min-width: var(--dimension-size-400); }\n.labelOnly { padding: 0 var(--dimension-space-100); }\n.iconAndLabel { padding: 0 var(--dimension-space-100) 0 var(--dimension-space-075); gap: var(--dimension-space-025); }\n\n/* ==========================================================================\n Sizes\n ========================================================================== */\n\n.sizeSM { height: var(--dimension-size-300); }\n.sizeSM.iconOnly { width: var(--dimension-size-300); min-width: var(--dimension-size-300); }\n.sizeSM.labelOnly { padding: 0 var(--dimension-space-075); }\n.sizeSM.iconAndLabel { padding: 0 var(--dimension-space-075) 0 var(--dimension-space-050); gap: 0; }\n\n.sizeXS { height: var(--dimension-size-200); }\n.sizeXS.iconOnly { width: var(--dimension-size-200); min-width: var(--dimension-size-200); }\n.sizeXS.labelOnly, .sizeXS.iconAndLabel { padding: 0 var(--dimension-space-025); gap: 0; }\n\n/* ==========================================================================\n Rounded (pill shape)\n ========================================================================== */\n\n.rounded { border-radius: var(--dimension-radius-half); }\n.rounded.labelOnly { padding-left: calc(var(--dimension-space-100) + var(--dimension-space-050)); padding-right: calc(var(--dimension-space-100) + var(--dimension-space-050)); }\n.rounded.iconAndLabel { padding-left: calc(var(--dimension-space-075) + var(--dimension-space-050)); padding-right: calc(var(--dimension-space-100) + var(--dimension-space-050)); }\n.rounded.sizeSM.labelOnly { padding-left: calc(var(--dimension-space-075) + var(--dimension-space-050)); padding-right: calc(var(--dimension-space-075) + var(--dimension-space-050)); }\n.rounded.sizeSM.iconAndLabel { padding-left: calc(var(--dimension-space-050) + var(--dimension-space-050)); padding-right: calc(var(--dimension-space-075) + var(--dimension-space-050)); }\n.rounded.iconOnly { width: var(--dimension-size-400); min-width: var(--dimension-size-400); padding: 0; }\n.rounded.sizeSM.iconOnly { width: var(--dimension-size-300); min-width: var(--dimension-size-300); }\n.rounded.sizeXS.iconOnly { width: var(--dimension-size-200); min-width: var(--dimension-size-200); }\n\n/* ==========================================================================\n Disabled\n ========================================================================== */\n\n.inactive { opacity: 0.5; cursor: not-allowed; pointer-events: none; }\n\n/* ==========================================================================\n Inner slots\n ========================================================================== */\n\n.toggle-btn__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n line-height: 0;\n}\n\n.toggle-btn__label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n","import { Component, Prop, Event, EventEmitter, h, Host } from '@stencil/core';\nimport type { IconSize } from '../Icon/Icon';\n\nexport type ToggleButtonElevation = 'none' | 'flat' | 'elevated' | 'floating';\nexport type ToggleButtonSize = 'md' | 'sm' | 'xs';\nexport type ToggleButtonBackground = 'faint' | 'medium' | 'bold' | 'strong' | 'always-dark';\n\nconst ICON_SIZE: Record<ToggleButtonSize, IconSize> = { md: 'md', sm: 'sm', xs: 'xs' };\n\n// Pixel size reference for internal layout (icon span font-size)\nconst ICON_PX: Record<ToggleButtonSize, number> = { md: 20, sm: 16, xs: 12 };\n\nconst TEXT_CLASS: Record<ToggleButtonSize, string> = {\n md: 'text-body-medium-emphasis',\n sm: 'text-body-small-emphasis',\n xs: 'text-caption-emphasis',\n};\n\n@Component({\n tag: 'ds-toggle-button',\n styleUrl: 'ToggleButton.css',\n scoped: true,\n})\nexport class ToggleButton {\n /**\n * Chrome level.\n * none — ghost (transparent, no border, no shadow)\n * flat — border only, transparent bg\n * elevated — bg-primary + shadow [default]\n * floating — bg-primary + FAB-strength shadow\n */\n @Prop() elevation: ToggleButtonElevation = 'elevated';\n\n /** Label text. */\n @Prop() label: string | undefined;\n\n /**\n * Icon name for <ds-icon>. Set via JS property.\n * @example el.icon = 'GridView';\n */\n @Prop() icon: string | undefined;\n\n /** Size: md (32px), sm (24px), xs (16px). */\n @Prop() size: ToggleButtonSize = 'md';\n\n /** Pill shape. */\n @Prop() rounded: boolean = false;\n\n /** Parent surface context — adjusts hover tokens for colored backgrounds. */\n @Prop() background: ToggleButtonBackground | undefined;\n\n /** Controlled pressed state. */\n @Prop({ mutable: true }) pressed: boolean = false;\n\n /** Disables interaction. */\n @Prop() inactive: boolean = false;\n\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | undefined;\n\n /** Emits the new pressed value (!pressed) on click. */\n @Event() dsChange!: EventEmitter<boolean>;\n\n private handleClick = () => {\n if (this.inactive) return;\n this.dsChange.emit(!this.pressed);\n };\n\n render() {\n const size = this.size;\n const iconSize = ICON_SIZE[size];\n const iconPx = ICON_PX[size];\n const textClass = TEXT_CLASS[size];\n const hasIcon = !!this.icon;\n const hasLabel = !!this.label;\n const isIconOnly = hasIcon && !hasLabel;\n const isLabelOnly = hasLabel && !hasIcon;\n const isIconAndLabel = hasIcon && hasLabel;\n const bg = this.background;\n const elevKey = this.elevation.charAt(0).toUpperCase() + this.elevation.slice(1);\n\n const cls: Record<string, boolean> = {\n 'toggle-btn': true,\n [`elevation${elevKey}`]: true,\n sizeSM: size === 'sm',\n sizeXS: size === 'xs',\n rounded: this.rounded,\n inactive: this.inactive,\n pressed: this.pressed,\n iconOnly: isIconOnly,\n labelOnly: isLabelOnly,\n iconAndLabel: isIconAndLabel,\n onMedium: bg === 'medium',\n onBold: bg === 'bold',\n onStrong: bg === 'strong',\n onAlwaysDark: bg === 'always-dark',\n };\n\n return (\n <Host>\n <button\n type=\"button\"\n class={cls}\n disabled={this.inactive}\n aria-label={this.ariaLabel ?? this.label ?? 'toggle'}\n aria-pressed={this.pressed}\n onClick={this.handleClick}\n >\n {hasIcon && (\n <span class=\"toggle-btn__icon\" style={{ fontSize: `${iconPx}px` }}>\n <ds-icon name={this.icon} size={iconSize} />\n </span>\n )}\n {hasLabel && (\n <span class={`toggle-btn__label ${textClass}`}>{this.label}</span>\n )}\n </button>\n </Host>\n );\n }\n}\n"],"mappings":"sGAAA,MAAMA,EAAkB,IAAM,8hLCO9B,MAAMC,EAAgD,CAAEC,GAAI,KAAMC,GAAI,KAAMC,GAAI,MAGhF,MAAMC,EAA4C,CAAEH,GAAI,GAAIC,GAAI,GAAIC,GAAI,IAExE,MAAME,EAA+C,CACnDJ,GAAI,4BACJC,GAAI,2BACJC,GAAI,yB,MAQOG,EAAYC,EAAA,MAAAD,UAAAE,EALzB,WAAAC,CAAAC,G,6EAaUC,KAAAC,UAAmC,WAYnCD,KAAAE,KAAyB,KAGzBF,KAAAG,QAAmB,MAMFH,KAAAI,QAAmB,MAGpCJ,KAAAK,SAAoB,MAOpBL,KAAAM,YAAc,KACpB,GAAIN,KAAKK,SAAU,OACnBL,KAAKO,SAASC,MAAMR,KAAKI,QAAQ,CAuDpC,CApDC,MAAAK,GACE,MAAMP,EAAOF,KAAKE,KAClB,MAAMQ,EAAWrB,EAAUa,GAC3B,MAAMS,EAASlB,EAAQS,GACvB,MAAMU,EAAYlB,EAAWQ,GAC7B,MAAMW,IAAYb,KAAKc,KACvB,MAAMC,IAAaf,KAAKgB,MACxB,MAAMC,EAAaJ,IAAYE,EAC/B,MAAMG,EAAcH,IAAaF,EACjC,MAAMM,EAAiBN,GAAWE,EAClC,MAAMK,EAAKpB,KAAKqB,WAChB,MAAMC,EAAUtB,KAAKC,UAAUsB,OAAO,GAAGC,cAAgBxB,KAAKC,UAAUwB,MAAM,GAE9E,MAAMC,EAA+B,CACnC,aAAc,KACd,CAAC,YAAYJ,KAAY,KACzBK,OAAQzB,IAAS,KACjB0B,OAAQ1B,IAAS,KACjBC,QAASH,KAAKG,QACdE,SAAUL,KAAKK,SACfD,QAASJ,KAAKI,QACdyB,SAAUZ,EACVa,UAAWZ,EACXa,aAAcZ,EACda,SAAUZ,IAAO,SACjBa,OAAQb,IAAO,OACfc,SAAUd,IAAO,SACjBe,aAAcf,IAAO,eAGvB,OACEgB,EAACC,EAAI,CAAAC,IAAA,4CACHF,EAAA,UAAAE,IAAA,2CACEC,KAAK,SACLC,MAAOd,EACPe,SAAUzC,KAAKK,SAAQ,aACXL,KAAK0C,WAAa1C,KAAKgB,OAAS,SAAQ,eACtChB,KAAKI,QACnBuC,QAAS3C,KAAKM,aAEbO,GACCuB,EAAA,QAAAE,IAAA,2CAAME,MAAM,mBAAmBI,MAAO,CAAEC,SAAU,GAAGlC,QACnDyB,EAAA,WAAAE,IAAA,2CAASQ,KAAM9C,KAAKc,KAAMZ,KAAMQ,KAGnCK,GACCqB,EAAA,QAAAE,IAAA,2CAAME,MAAO,qBAAqB5B,KAAcZ,KAAKgB,Q","ignoreList":[]}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from '../../stencil-public-runtime';
|
|
2
|
-
export type SidebarWidth = 'mini' | 'default' | number;
|
|
3
|
-
export declare class Sidebar {
|
|
4
|
-
el: HTMLElement;
|
|
5
|
-
collapsed: boolean;
|
|
6
|
-
width: SidebarWidth;
|
|
7
|
-
resizable: boolean;
|
|
8
|
-
mobile: boolean;
|
|
9
|
-
private isResizing;
|
|
10
|
-
dsToggle: EventEmitter<void>;
|
|
11
|
-
dsWidthChange: EventEmitter<SidebarWidth>;
|
|
12
|
-
private triggerEl;
|
|
13
|
-
private escapeHandler;
|
|
14
|
-
private tabHandler;
|
|
15
|
-
private mouseMoveHandler;
|
|
16
|
-
private mouseUpHandler;
|
|
17
|
-
private get resolvedWidth();
|
|
18
|
-
private get isMobileOpen();
|
|
19
|
-
onCollapsedChange(): void;
|
|
20
|
-
componentDidLoad(): void;
|
|
21
|
-
disconnectedCallback(): void;
|
|
22
|
-
private setupMobileFocusTrap;
|
|
23
|
-
private teardownMobileFocusTrap;
|
|
24
|
-
private cleanupMouseHandlers;
|
|
25
|
-
private handleResizeMouseDown;
|
|
26
|
-
private handleResizeKeyDown;
|
|
27
|
-
render(): any;
|
|
28
|
-
}
|
|
29
|
-
//# sourceMappingURL=Sidebar.d.ts.map
|
package/src/react/ds-sidebar.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* This file was automatically generated by the Stencil React Output Target.
|
|
5
|
-
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/* eslint-disable */
|
|
9
|
-
|
|
10
|
-
import type { EventName, StencilReactComponent } from '@stencil/react-output-target/runtime';
|
|
11
|
-
import { createComponent } from '@stencil/react-output-target/runtime';
|
|
12
|
-
import React from 'react';
|
|
13
|
-
|
|
14
|
-
import { type DsSidebarCustomEvent, type SidebarWidth } from "@ds-mo/ui";
|
|
15
|
-
import type { Components } from "@ds-mo/ui/dist/components";
|
|
16
|
-
import { DsSidebar as DsSidebarElement, defineCustomElement as defineDsSidebar } from "@ds-mo/ui/dist/components/ds-sidebar.js";
|
|
17
|
-
|
|
18
|
-
export type DsSidebarEvents = {
|
|
19
|
-
onDsToggle: EventName<DsSidebarCustomEvent<void>>,
|
|
20
|
-
onDsWidthChange: EventName<DsSidebarCustomEvent<SidebarWidth>>
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const DsSidebar: StencilReactComponent<DsSidebarElement, DsSidebarEvents, Components.DsSidebar> = /*@__PURE__*/ createComponent<DsSidebarElement, DsSidebarEvents, Components.DsSidebar>({
|
|
24
|
-
tagName: 'ds-sidebar',
|
|
25
|
-
elementClass: DsSidebarElement,
|
|
26
|
-
// @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project.
|
|
27
|
-
react: React,
|
|
28
|
-
events: {
|
|
29
|
-
onDsToggle: 'dsToggle',
|
|
30
|
-
onDsWidthChange: 'dsWidthChange'
|
|
31
|
-
} as DsSidebarEvents,
|
|
32
|
-
defineCustomElement: defineDsSidebar
|
|
33
|
-
});
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { Component, Prop, State, Event, EventEmitter, Element, Watch, h, Host } from '@stencil/core';
|
|
2
|
-
|
|
3
|
-
export type SidebarWidth = 'mini' | 'default' | number;
|
|
4
|
-
|
|
5
|
-
const MINI_WIDTH = 56;
|
|
6
|
-
const DEFAULT_WIDTH = 240;
|
|
7
|
-
const MAX_WIDTH = 480;
|
|
8
|
-
const MIN_DRAG_WIDTH = 180;
|
|
9
|
-
const SNAP_THRESHOLD = 140;
|
|
10
|
-
const KEYBOARD_STEP = 16;
|
|
11
|
-
|
|
12
|
-
const FOCUSABLE_SEL = [
|
|
13
|
-
'a[href]',
|
|
14
|
-
'button:not([disabled])',
|
|
15
|
-
'input:not([disabled])',
|
|
16
|
-
'select:not([disabled])',
|
|
17
|
-
'textarea:not([disabled])',
|
|
18
|
-
'[tabindex]:not([tabindex="-1"])',
|
|
19
|
-
].join(',');
|
|
20
|
-
|
|
21
|
-
@Component({
|
|
22
|
-
tag: 'ds-sidebar',
|
|
23
|
-
styleUrl: 'Sidebar.css',
|
|
24
|
-
scoped: true,
|
|
25
|
-
})
|
|
26
|
-
export class Sidebar {
|
|
27
|
-
@Element() el!: HTMLElement;
|
|
28
|
-
|
|
29
|
-
@Prop({ mutable: true }) collapsed: boolean = false;
|
|
30
|
-
@Prop() width: SidebarWidth = 'default';
|
|
31
|
-
@Prop() resizable: boolean = true;
|
|
32
|
-
@Prop() mobile: boolean = false;
|
|
33
|
-
|
|
34
|
-
@State() private isResizing: boolean = false;
|
|
35
|
-
|
|
36
|
-
@Event() dsToggle!: EventEmitter<void>;
|
|
37
|
-
@Event() dsWidthChange!: EventEmitter<SidebarWidth>;
|
|
38
|
-
|
|
39
|
-
private triggerEl: HTMLElement | null = null;
|
|
40
|
-
private escapeHandler: ((e: KeyboardEvent) => void) | null = null;
|
|
41
|
-
private tabHandler: ((e: KeyboardEvent) => void) | null = null;
|
|
42
|
-
private mouseMoveHandler: ((e: MouseEvent) => void) | null = null;
|
|
43
|
-
private mouseUpHandler: (() => void) | null = null;
|
|
44
|
-
|
|
45
|
-
private get resolvedWidth(): number {
|
|
46
|
-
if (this.width === 'mini') return MINI_WIDTH;
|
|
47
|
-
if (this.width === 'default') return DEFAULT_WIDTH;
|
|
48
|
-
return this.width as number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private get isMobileOpen(): boolean {
|
|
52
|
-
return this.mobile && !this.collapsed;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@Watch('collapsed')
|
|
56
|
-
onCollapsedChange() {
|
|
57
|
-
if (this.isMobileOpen) {
|
|
58
|
-
this.setupMobileFocusTrap();
|
|
59
|
-
} else {
|
|
60
|
-
this.teardownMobileFocusTrap();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
componentDidLoad() {
|
|
65
|
-
if (this.isMobileOpen) {
|
|
66
|
-
this.setupMobileFocusTrap();
|
|
67
|
-
}
|
|
68
|
-
if (this.mobile) {
|
|
69
|
-
this.escapeHandler = (e: KeyboardEvent) => {
|
|
70
|
-
if (e.key === 'Escape' && !this.collapsed) {
|
|
71
|
-
this.dsToggle.emit();
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
document.addEventListener('keydown', this.escapeHandler);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
disconnectedCallback() {
|
|
79
|
-
this.teardownMobileFocusTrap();
|
|
80
|
-
if (this.escapeHandler) {
|
|
81
|
-
document.removeEventListener('keydown', this.escapeHandler);
|
|
82
|
-
this.escapeHandler = null;
|
|
83
|
-
}
|
|
84
|
-
this.cleanupMouseHandlers();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private setupMobileFocusTrap() {
|
|
88
|
-
this.triggerEl = document.activeElement as HTMLElement | null;
|
|
89
|
-
|
|
90
|
-
const focusables = () =>
|
|
91
|
-
Array.from(this.el.querySelectorAll<HTMLElement>(FOCUSABLE_SEL)).filter(
|
|
92
|
-
el => !el.hasAttribute('aria-hidden') && el.offsetParent !== null,
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const initial = focusables();
|
|
96
|
-
(initial[0] ?? this.el).focus();
|
|
97
|
-
|
|
98
|
-
this.tabHandler = (e: KeyboardEvent) => {
|
|
99
|
-
if (e.key !== 'Tab') return;
|
|
100
|
-
const items = focusables();
|
|
101
|
-
if (!items.length) { e.preventDefault(); this.el.focus(); return; }
|
|
102
|
-
const first = items[0];
|
|
103
|
-
const last = items[items.length - 1];
|
|
104
|
-
const active = document.activeElement as HTMLElement | null;
|
|
105
|
-
if (e.shiftKey && (active === first || !this.el.contains(active))) {
|
|
106
|
-
e.preventDefault(); last.focus();
|
|
107
|
-
} else if (!e.shiftKey && (active === last || !this.el.contains(active))) {
|
|
108
|
-
e.preventDefault(); first.focus();
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
document.addEventListener('keydown', this.tabHandler);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private teardownMobileFocusTrap() {
|
|
115
|
-
if (this.tabHandler) {
|
|
116
|
-
document.removeEventListener('keydown', this.tabHandler);
|
|
117
|
-
this.tabHandler = null;
|
|
118
|
-
}
|
|
119
|
-
this.triggerEl?.focus?.();
|
|
120
|
-
this.triggerEl = null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
private cleanupMouseHandlers() {
|
|
124
|
-
if (this.mouseMoveHandler) document.removeEventListener('mousemove', this.mouseMoveHandler);
|
|
125
|
-
if (this.mouseUpHandler) document.removeEventListener('mouseup', this.mouseUpHandler);
|
|
126
|
-
this.mouseMoveHandler = null;
|
|
127
|
-
this.mouseUpHandler = null;
|
|
128
|
-
document.body.style.cursor = '';
|
|
129
|
-
document.body.style.userSelect = '';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private handleResizeMouseDown(e: MouseEvent) {
|
|
133
|
-
if (!this.resizable) return;
|
|
134
|
-
e.preventDefault();
|
|
135
|
-
this.isResizing = true;
|
|
136
|
-
|
|
137
|
-
this.mouseMoveHandler = (ev: MouseEvent) => {
|
|
138
|
-
const newW = ev.clientX;
|
|
139
|
-
if (newW < SNAP_THRESHOLD) {
|
|
140
|
-
this.dsWidthChange.emit('mini');
|
|
141
|
-
} else if (newW < MIN_DRAG_WIDTH) {
|
|
142
|
-
this.dsWidthChange.emit('default');
|
|
143
|
-
} else {
|
|
144
|
-
this.dsWidthChange.emit(newW);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
this.mouseUpHandler = () => {
|
|
149
|
-
this.isResizing = false;
|
|
150
|
-
this.cleanupMouseHandlers();
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
document.body.style.cursor = 'col-resize';
|
|
154
|
-
document.body.style.userSelect = 'none';
|
|
155
|
-
document.addEventListener('mousemove', this.mouseMoveHandler);
|
|
156
|
-
document.addEventListener('mouseup', this.mouseUpHandler);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private handleResizeKeyDown(e: KeyboardEvent) {
|
|
160
|
-
if (!this.resizable) return;
|
|
161
|
-
const w = this.width;
|
|
162
|
-
const resolved = this.resolvedWidth;
|
|
163
|
-
|
|
164
|
-
switch (e.key) {
|
|
165
|
-
case 'ArrowLeft': {
|
|
166
|
-
e.preventDefault();
|
|
167
|
-
if (w === 'mini') return;
|
|
168
|
-
const next = resolved - KEYBOARD_STEP;
|
|
169
|
-
this.dsWidthChange.emit(next < MIN_DRAG_WIDTH ? 'mini' : next);
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
case 'ArrowRight': {
|
|
173
|
-
e.preventDefault();
|
|
174
|
-
if (w === 'mini') { this.dsWidthChange.emit('default'); return; }
|
|
175
|
-
this.dsWidthChange.emit(Math.min(resolved + KEYBOARD_STEP, MAX_WIDTH));
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
case 'Home':
|
|
179
|
-
e.preventDefault();
|
|
180
|
-
this.dsWidthChange.emit('mini');
|
|
181
|
-
break;
|
|
182
|
-
case 'End':
|
|
183
|
-
e.preventDefault();
|
|
184
|
-
this.dsWidthChange.emit(MAX_WIDTH);
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
render() {
|
|
190
|
-
if (this.collapsed && !this.mobile) return null;
|
|
191
|
-
|
|
192
|
-
const isMini = this.width === 'mini';
|
|
193
|
-
const isMobileOpen = this.isMobileOpen;
|
|
194
|
-
const sidebarWidth = this.collapsed ? 0 : this.resolvedWidth;
|
|
195
|
-
|
|
196
|
-
const dialogProps = isMobileOpen
|
|
197
|
-
? { role: 'dialog', 'aria-modal': 'true', tabIndex: -1 }
|
|
198
|
-
: { role: 'navigation' };
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<Host>
|
|
202
|
-
{this.mobile && !this.collapsed && (
|
|
203
|
-
<div
|
|
204
|
-
class="backdrop"
|
|
205
|
-
onClick={() => this.dsToggle.emit()}
|
|
206
|
-
aria-hidden="true"
|
|
207
|
-
/>
|
|
208
|
-
)}
|
|
209
|
-
<aside
|
|
210
|
-
class={{
|
|
211
|
-
sidebar: true,
|
|
212
|
-
'sidebar--mini': isMini,
|
|
213
|
-
'sidebar--mobile': this.mobile,
|
|
214
|
-
'sidebar--collapsed': this.collapsed,
|
|
215
|
-
'sidebar--resizing': this.isResizing,
|
|
216
|
-
}}
|
|
217
|
-
style={{ width: `${sidebarWidth}px` }}
|
|
218
|
-
aria-label="Sidebar"
|
|
219
|
-
{...dialogProps}
|
|
220
|
-
>
|
|
221
|
-
<div class="sidebar__content">
|
|
222
|
-
<slot />
|
|
223
|
-
</div>
|
|
224
|
-
<div class="sidebar__footer">
|
|
225
|
-
<slot name="footer" />
|
|
226
|
-
</div>
|
|
227
|
-
{this.resizable && !this.mobile && (
|
|
228
|
-
<div
|
|
229
|
-
class="sidebar__resize-handle"
|
|
230
|
-
onMouseDown={(e: MouseEvent) => this.handleResizeMouseDown(e)}
|
|
231
|
-
onKeyDown={(e: KeyboardEvent) => this.handleResizeKeyDown(e)}
|
|
232
|
-
role="separator"
|
|
233
|
-
aria-orientation="vertical"
|
|
234
|
-
aria-label="Resize sidebar"
|
|
235
|
-
aria-valuenow={this.resolvedWidth}
|
|
236
|
-
aria-valuemin={MINI_WIDTH}
|
|
237
|
-
aria-valuemax={MAX_WIDTH}
|
|
238
|
-
tabIndex={0}
|
|
239
|
-
/>
|
|
240
|
-
)}
|
|
241
|
-
</aside>
|
|
242
|
-
</Host>
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
File without changes
|