@aquera/nile-elements 0.0.80 → 0.0.81
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 +3 -0
- package/dist/fixture-6f853cbd.esm.js +569 -0
- package/dist/fixture-b1476eef.cjs.js +395 -0
- package/dist/fixture-b1476eef.cjs.js.map +1 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.iife.js +171 -65
- package/dist/internal/drag.cjs.js +2 -0
- package/dist/internal/drag.cjs.js.map +1 -0
- package/dist/internal/drag.esm.js +1 -0
- package/dist/internal/math.cjs.js +2 -0
- package/dist/internal/math.cjs.js.map +1 -0
- package/dist/internal/math.esm.js +1 -0
- package/dist/nile-checkbox/nile-checkbox.test.cjs.js +1 -394
- package/dist/nile-checkbox/nile-checkbox.test.cjs.js.map +1 -1
- package/dist/nile-checkbox/nile-checkbox.test.esm.js +3 -571
- package/dist/nile-empty-state/nile-empty-state.test.cjs.js +2 -0
- package/dist/nile-empty-state/nile-empty-state.test.cjs.js.map +1 -0
- package/dist/nile-empty-state/nile-empty-state.test.esm.js +9 -0
- package/dist/nile-hero/nile-hero.test.cjs.js +2 -0
- package/dist/nile-hero/nile-hero.test.cjs.js.map +1 -0
- package/dist/nile-hero/nile-hero.test.esm.js +14 -0
- package/dist/nile-split-panel/index.cjs.js +2 -0
- package/dist/nile-split-panel/index.cjs.js.map +1 -0
- package/dist/nile-split-panel/index.esm.js +1 -0
- package/dist/nile-split-panel/nile-split-panel.cjs.js +2 -0
- package/dist/nile-split-panel/nile-split-panel.cjs.js.map +1 -0
- package/dist/nile-split-panel/nile-split-panel.css.cjs.js +2 -0
- package/dist/nile-split-panel/nile-split-panel.css.cjs.js.map +1 -0
- package/dist/nile-split-panel/nile-split-panel.css.esm.js +87 -0
- package/dist/nile-split-panel/nile-split-panel.esm.js +21 -0
- package/dist/nile-stepper/nile-stepper.cjs.js +1 -1
- package/dist/nile-stepper/nile-stepper.cjs.js.map +1 -1
- package/dist/nile-stepper/nile-stepper.esm.js +1 -1
- package/dist/nile-stepper/nile-stepper.test.cjs.js +2 -0
- package/dist/nile-stepper/nile-stepper.test.cjs.js.map +1 -0
- package/dist/nile-stepper/nile-stepper.test.esm.js +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal/drag.d.ts +15 -0
- package/dist/src/internal/drag.js +28 -0
- package/dist/src/internal/drag.js.map +1 -0
- package/dist/src/internal/math.d.ts +2 -0
- package/dist/src/internal/math.js +12 -0
- package/dist/src/internal/math.js.map +1 -0
- package/dist/src/nile-empty-state/nile-empty-state.test.d.ts +1 -0
- package/dist/src/nile-empty-state/nile-empty-state.test.js +63 -0
- package/dist/src/nile-empty-state/nile-empty-state.test.js.map +1 -0
- package/dist/src/nile-hero/nile-hero.test.d.ts +1 -0
- package/dist/src/nile-hero/nile-hero.test.js +42 -0
- package/dist/src/nile-hero/nile-hero.test.js.map +1 -0
- package/dist/src/nile-split-panel/index.d.ts +1 -0
- package/dist/src/nile-split-panel/index.js +2 -0
- package/dist/src/nile-split-panel/index.js.map +1 -0
- package/dist/src/nile-split-panel/nile-split-panel.css.d.ts +12 -0
- package/dist/src/nile-split-panel/nile-split-panel.css.js +99 -0
- package/dist/src/nile-split-panel/nile-split-panel.css.js.map +1 -0
- package/dist/src/nile-split-panel/nile-split-panel.d.ts +78 -0
- package/dist/src/nile-split-panel/nile-split-panel.js +251 -0
- package/dist/src/nile-split-panel/nile-split-panel.js.map +1 -0
- package/dist/src/nile-stepper/nile-stepper.js +1 -1
- package/dist/src/nile-stepper/nile-stepper.js.map +1 -1
- package/dist/src/nile-stepper/nile-stepper.test.d.ts +0 -0
- package/dist/src/nile-stepper/nile-stepper.test.js +165 -0
- package/dist/src/nile-stepper/nile-stepper.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/internal/drag.ts +45 -0
- package/src/internal/math.ts +14 -0
- package/src/nile-empty-state/nile-empty-state.test.ts +69 -0
- package/src/nile-hero/nile-hero.test.ts +45 -0
- package/src/nile-split-panel/index.ts +1 -0
- package/src/nile-split-panel/nile-split-panel.css.ts +101 -0
- package/src/nile-split-panel/nile-split-panel.ts +285 -0
- package/src/nile-stepper/nile-stepper.test.ts +174 -0
- package/src/nile-stepper/nile-stepper.ts +2 -2
package/package.json
CHANGED
package/src/index.ts
CHANGED
@@ -63,4 +63,5 @@ export { NileStepperItem } from './nile-stepper-item';
|
|
63
63
|
export { NileStepper } from './nile-stepper';
|
64
64
|
export { NileHeroHeader } from './nile-hero-header';
|
65
65
|
export { NileVerticalStepperItem } from './nile-vertical-stepper-item';
|
66
|
-
export { NileFormatDate } from './nile-format-date';
|
66
|
+
export { NileFormatDate } from './nile-format-date';
|
67
|
+
export { NileSplitPanel } from './nile-split-panel';
|
@@ -0,0 +1,45 @@
|
|
1
|
+
interface DragOptions {
|
2
|
+
/** Callback that runs as dragging occurs. */
|
3
|
+
onMove: (x: number, y: number) => void;
|
4
|
+
/** Callback that runs when dragging stops. */
|
5
|
+
onStop: () => void;
|
6
|
+
/**
|
7
|
+
* When an initial event is passed, the first drag will be triggered immediately using the coordinates therein. This
|
8
|
+
* is useful when the drag is initiated by a mousedown/touchstart event but you want the initial "click" to activate
|
9
|
+
* a drag (e.g. positioning a handle initially at the click target).
|
10
|
+
*/
|
11
|
+
initialEvent: PointerEvent;
|
12
|
+
}
|
13
|
+
|
14
|
+
/** Begins listening for dragging. */
|
15
|
+
export function drag(container: HTMLElement, options?: Partial<DragOptions>) {
|
16
|
+
function move(pointerEvent: PointerEvent) {
|
17
|
+
const dims = container.getBoundingClientRect();
|
18
|
+
const defaultView = container.ownerDocument.defaultView!;
|
19
|
+
const offsetX = dims.left + defaultView.pageXOffset;
|
20
|
+
const offsetY = dims.top + defaultView.pageYOffset;
|
21
|
+
const x = pointerEvent.pageX - offsetX;
|
22
|
+
const y = pointerEvent.pageY - offsetY;
|
23
|
+
|
24
|
+
if (options?.onMove) {
|
25
|
+
options.onMove(x, y);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
function stop() {
|
30
|
+
document.removeEventListener('pointermove', move);
|
31
|
+
document.removeEventListener('pointerup', stop);
|
32
|
+
|
33
|
+
if (options?.onStop) {
|
34
|
+
options.onStop();
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
document.addEventListener('pointermove', move, { passive: true });
|
39
|
+
document.addEventListener('pointerup', stop);
|
40
|
+
|
41
|
+
// If an initial event is set, trigger the first drag immediately
|
42
|
+
if (options?.initialEvent instanceof PointerEvent) {
|
43
|
+
move(options.initialEvent);
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
/** Ensures a number stays within a minimum and maximum value */
|
2
|
+
export function clamp(value: number, min: number, max: number) {
|
3
|
+
const noNegativeZero = (n: number) => (Object.is(n, -0) ? 0 : n);
|
4
|
+
|
5
|
+
if (value < min) {
|
6
|
+
return noNegativeZero(min);
|
7
|
+
}
|
8
|
+
|
9
|
+
if (value > max) {
|
10
|
+
return noNegativeZero(max);
|
11
|
+
}
|
12
|
+
|
13
|
+
return noNegativeZero(value);
|
14
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import { fixture,html,expect } from "@open-wc/testing";
|
2
|
+
import './nile-empty-state';
|
3
|
+
import { NileEmptyState } from "./nile-empty-state";
|
4
|
+
|
5
|
+
describe('NileEmptyState',()=>{
|
6
|
+
it('renders correctly with default properties',async ()=>{
|
7
|
+
const el=await fixture<NileEmptyState>(html`<nile-empty-state></nile-empty-state>`)
|
8
|
+
expect(el.size).to.equal('md');
|
9
|
+
expect(el.variant).to.equal('tonal');
|
10
|
+
expect(el.icon).to.equal('error');
|
11
|
+
expect(el.grayscale).to.be.false;
|
12
|
+
expect(el.text).to.equal('Empty State');
|
13
|
+
expect(el.subText).to.equal('No Data');
|
14
|
+
})
|
15
|
+
|
16
|
+
it('should reflect properties to attributes', async () => {
|
17
|
+
const iconName="action"
|
18
|
+
const el = await fixture<NileEmptyState>(html`
|
19
|
+
<nile-empty-state size="sm" variant="flat" icon=${iconName} grayscale text="Test state text" sub-text="Test sub text"></nile-empty-state>
|
20
|
+
`);
|
21
|
+
expect(el.getAttribute('size')).to.equal('sm');
|
22
|
+
expect(el.getAttribute('variant')).to.equal('flat');
|
23
|
+
expect(el.getAttribute('icon')).to.equal(iconName);
|
24
|
+
expect(el.hasAttribute('grayscale')).to.be.true;
|
25
|
+
expect(el.getAttribute('text')).to.equal('Test state text');
|
26
|
+
expect(el.getAttribute('sub-text')).to.equal('Test sub text');
|
27
|
+
});
|
28
|
+
|
29
|
+
it('should render different sizes correctly', async () => {
|
30
|
+
const sizes:Array<'sm' | 'md' | 'lg' >= ['sm', 'md', 'lg'];
|
31
|
+
for (const size of sizes) {
|
32
|
+
const el = await fixture<NileEmptyState>(html`<nile-empty-state size="${size}"></nile-empty-state>`);
|
33
|
+
const container = el.shadowRoot!.querySelector('.empty-state')!;
|
34
|
+
expect(container.classList.contains(`empty-state--${size}`)).to.be.true;
|
35
|
+
}
|
36
|
+
});
|
37
|
+
|
38
|
+
it('should render different variants correctly', async () => {
|
39
|
+
const variants :Array<'flat' | 'content' | 'tonal' >= ['flat', 'content', 'tonal'];
|
40
|
+
for (const variant of variants) {
|
41
|
+
const el = await fixture<NileEmptyState>(html`<nile-empty-state variant="${variant}"></nile-empty-state>`);
|
42
|
+
const container = el.shadowRoot!.querySelector(`.empty-state__body--${variant}`)!;
|
43
|
+
expect(container).to.exist;
|
44
|
+
}
|
45
|
+
});
|
46
|
+
|
47
|
+
it('should render text properties correctly', async () => {
|
48
|
+
const sampleText='Custom Text'
|
49
|
+
const sampleSubText="Custom SubText"
|
50
|
+
const el = await fixture<NileEmptyState>(html`
|
51
|
+
<nile-empty-state text=${sampleText} sub-text=${sampleSubText}></nile-empty-state>
|
52
|
+
`);
|
53
|
+
const textEl = el.shadowRoot!.querySelector('.empty-state__text')!;
|
54
|
+
const subTextEl = el.shadowRoot!.querySelector('.empty-state__subtext')!;
|
55
|
+
expect(textEl.textContent).to.equal(sampleText);
|
56
|
+
expect(subTextEl.textContent).to.equal(sampleSubText);
|
57
|
+
});
|
58
|
+
|
59
|
+
it('should render slot content', async () => {
|
60
|
+
const el = await fixture<NileEmptyState>(html`
|
61
|
+
<nile-empty-state>
|
62
|
+
<div class="slot-content">Slot Content</div>
|
63
|
+
</nile-empty-state>
|
64
|
+
`);
|
65
|
+
const slotContent = el.querySelector('.slot-content')!;
|
66
|
+
expect(slotContent).to.exist;
|
67
|
+
expect(slotContent.textContent).to.equal('Slot Content');
|
68
|
+
});
|
69
|
+
})
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import { fixture, html, expect, oneEvent } from '@open-wc/testing';
|
2
|
+
import './nile-hero';
|
3
|
+
import { NileHero } from './nile-hero';
|
4
|
+
|
5
|
+
describe('NileHero', () => {
|
6
|
+
it('renders correctly with default properties', async () => {
|
7
|
+
const el = await fixture<NileHero>(html`
|
8
|
+
<nile-hero icon="error" hero-text="Test Hero">
|
9
|
+
<nile-page-header
|
10
|
+
heading="I am heading"
|
11
|
+
sub-heading="I am subheading"
|
12
|
+
></nile-page-header>
|
13
|
+
</nile-hero>`);
|
14
|
+
expect(el.getAttribute('icon')).to.equal('error');
|
15
|
+
expect(el.hasAttribute('collapse')).to.be.false;
|
16
|
+
expect(el.getAttribute('img-src')).to.equal('');
|
17
|
+
expect(el.getAttribute('hero-text')).to.equal('Test Hero');
|
18
|
+
expect(el).shadowDom.to.equalSnapshot();
|
19
|
+
});
|
20
|
+
|
21
|
+
it('checks for collapsed state', async () => {
|
22
|
+
const el = await fixture<NileHero>(html`<nile-hero></nile-hero>`);
|
23
|
+
el.collapse = true;
|
24
|
+
await el.updateComplete;
|
25
|
+
expect(el.collapse).to.be.true;
|
26
|
+
el.collapse=true;
|
27
|
+
await el.updateComplete;
|
28
|
+
const heroContainer=el.shadowRoot?.querySelector('.hero__container');
|
29
|
+
expect(heroContainer?.classList.contains('hero__container--collapsed')).to.be.true;
|
30
|
+
});
|
31
|
+
|
32
|
+
it('should render slot content', async () => {
|
33
|
+
const el = await fixture<NileHero>(html`
|
34
|
+
<nile-hero>
|
35
|
+
<nile-page-header
|
36
|
+
heading="I am heading"
|
37
|
+
sub-heading="I am subheading"
|
38
|
+
></nile-page-header>
|
39
|
+
</nile-hero>
|
40
|
+
`);
|
41
|
+
await el.updateComplete;
|
42
|
+
const NilePageHeader = el.querySelector('nile-page-header')!;
|
43
|
+
expect(NilePageHeader).to.exist;
|
44
|
+
});
|
45
|
+
})
|
@@ -0,0 +1 @@
|
|
1
|
+
export { NileSplitPanel } from './nile-split-panel';
|
@@ -0,0 +1,101 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright Aquera Inc 2023
|
3
|
+
*
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
6
|
+
*/
|
7
|
+
|
8
|
+
import {css} from 'lit-element';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* SplitPanel CSS
|
12
|
+
*/
|
13
|
+
export const styles = css`
|
14
|
+
:host *,
|
15
|
+
:host *::before,
|
16
|
+
:host *::after {
|
17
|
+
box-sizing: inherit;
|
18
|
+
}
|
19
|
+
|
20
|
+
[hidden] {
|
21
|
+
display: none !important;
|
22
|
+
}
|
23
|
+
|
24
|
+
:host {
|
25
|
+
box-sizing: border-box;
|
26
|
+
--divider-width: 4px;
|
27
|
+
--divider-hit-area: 12px;
|
28
|
+
--min: 0%;
|
29
|
+
--max: 100%;
|
30
|
+
|
31
|
+
display: grid;
|
32
|
+
}
|
33
|
+
|
34
|
+
.start,
|
35
|
+
.end {
|
36
|
+
overflow: hidden;
|
37
|
+
}
|
38
|
+
|
39
|
+
.divider {
|
40
|
+
flex: 0 0 var(--divider-width);
|
41
|
+
display: flex;
|
42
|
+
position: relative;
|
43
|
+
align-items: center;
|
44
|
+
justify-content: center;
|
45
|
+
background-color: var(--nile-colors-neutral-100);
|
46
|
+
color: var(--nile-colors-dark-900);
|
47
|
+
z-index: 1;
|
48
|
+
}
|
49
|
+
|
50
|
+
.divider:focus {
|
51
|
+
outline: none;
|
52
|
+
}
|
53
|
+
|
54
|
+
:host(:not([disabled])) .divider:focus-visible {
|
55
|
+
background-color: var(--nile-colors-blue-500);
|
56
|
+
color: var(--nile-colors-white-base);
|
57
|
+
}
|
58
|
+
|
59
|
+
:host([disabled]) .divider {
|
60
|
+
cursor: not-allowed;
|
61
|
+
}
|
62
|
+
|
63
|
+
/* Horizontal */
|
64
|
+
:host(:not([vertical], [disabled])) .divider {
|
65
|
+
cursor: col-resize;
|
66
|
+
}
|
67
|
+
|
68
|
+
:host(:not([vertical])) .divider::after {
|
69
|
+
display: flex;
|
70
|
+
content: '';
|
71
|
+
position: absolute;
|
72
|
+
height: 100%;
|
73
|
+
left: calc(var(--divider-hit-area) / -2 + var(--divider-width) / 2);
|
74
|
+
width: var(--divider-hit-area);
|
75
|
+
}
|
76
|
+
|
77
|
+
/* Vertical */
|
78
|
+
:host([vertical]) {
|
79
|
+
flex-direction: column;
|
80
|
+
}
|
81
|
+
|
82
|
+
:host([vertical]:not([disabled])) .divider {
|
83
|
+
cursor: row-resize;
|
84
|
+
}
|
85
|
+
|
86
|
+
:host([vertical]) .divider::after {
|
87
|
+
content: '';
|
88
|
+
position: absolute;
|
89
|
+
width: 100%;
|
90
|
+
top: calc(var(--divider-hit-area) / -2 + var(--divider-width) / 2);
|
91
|
+
height: var(--divider-hit-area);
|
92
|
+
}
|
93
|
+
|
94
|
+
@media (forced-colors: active) {
|
95
|
+
.divider {
|
96
|
+
outline: solid 1px transparent;
|
97
|
+
}
|
98
|
+
}
|
99
|
+
`;
|
100
|
+
|
101
|
+
export default [styles];
|
@@ -0,0 +1,285 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright Aquera Inc 2023
|
3
|
+
*
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
6
|
+
*/
|
7
|
+
|
8
|
+
import {LitElement, CSSResultArray, TemplateResult} from 'lit-element';
|
9
|
+
import {styles} from './nile-split-panel.css';
|
10
|
+
import NileElement from '../internal/nile-element';
|
11
|
+
|
12
|
+
|
13
|
+
import { clamp } from '../internal/math';
|
14
|
+
import { customElement, property, query } from 'lit/decorators.js';
|
15
|
+
import { drag } from '../internal/drag';
|
16
|
+
import { html } from 'lit';
|
17
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
18
|
+
import { watch } from '../internal/watch';
|
19
|
+
import type { CSSResultGroup } from 'lit';
|
20
|
+
|
21
|
+
/**
|
22
|
+
* @summary Split panels display two adjacent panels, allowing the user to reposition them.
|
23
|
+
*
|
24
|
+
* @event nile-reposition - Emitted when the divider's position changes.
|
25
|
+
*
|
26
|
+
* @slot start - Content to place in the start panel.
|
27
|
+
* @slot end - Content to place in the end panel.
|
28
|
+
* @slot divider - The divider. Useful for slotting in a custom icon that renders as a handle.
|
29
|
+
*
|
30
|
+
* @csspart start - The start panel.
|
31
|
+
* @csspart end - The end panel.
|
32
|
+
* @csspart panel - Targets both the start and end panels.
|
33
|
+
* @csspart divider - The divider that separates the start and end panels.
|
34
|
+
*
|
35
|
+
* @cssproperty [--divider-width=4px] - The width of the visible divider.
|
36
|
+
* @cssproperty [--divider-hit-area=12px] - The invisible region around the divider where dragging can occur. This is
|
37
|
+
* usually wider than the divider to facilitate easier dragging.
|
38
|
+
* @cssproperty [--min=0] - The minimum allowed size of the primary panel.
|
39
|
+
* @cssproperty [--max=100%] - The maximum allowed size of the primary panel.
|
40
|
+
*/
|
41
|
+
@customElement('nile-split-panel')
|
42
|
+
export class NileSplitPanel extends NileElement {
|
43
|
+
|
44
|
+
static styles: CSSResultGroup = styles;
|
45
|
+
|
46
|
+
private cachedPositionInPixels: number;
|
47
|
+
private resizeObserver: ResizeObserver;
|
48
|
+
private size: number;
|
49
|
+
|
50
|
+
@query('.divider') divider: HTMLElement;
|
51
|
+
|
52
|
+
/**
|
53
|
+
* The current position of the divider from the primary panel's edge as a percentage 0-100. Defaults to 50% of the
|
54
|
+
* container's initial size.
|
55
|
+
*/
|
56
|
+
@property({ type: Number, reflect: true }) position = 50;
|
57
|
+
|
58
|
+
/** The current position of the divider from the primary panel's edge in pixels. */
|
59
|
+
@property({ attribute: 'position-in-pixels', type: Number }) positionInPixels: number;
|
60
|
+
|
61
|
+
/** Draws the split panel in a vertical orientation with the start and end panels stacked. */
|
62
|
+
@property({ type: Boolean, reflect: true }) vertical = false;
|
63
|
+
|
64
|
+
/** Disables resizing. Note that the position may still change as a result of resizing the host element. */
|
65
|
+
@property({ type: Boolean, reflect: true }) disabled = false;
|
66
|
+
|
67
|
+
/**
|
68
|
+
* If no primary panel is designated, both panels will resize proportionally when the host element is resized. If a
|
69
|
+
* primary panel is designated, it will maintain its size and the other panel will grow or shrink as needed when the
|
70
|
+
* host element is resized.
|
71
|
+
*/
|
72
|
+
@property() primary?: 'start' | 'end';
|
73
|
+
|
74
|
+
/**
|
75
|
+
* One or more space-separated values at which the divider should snap. Values can be in pixels or percentages, e.g.
|
76
|
+
* `"100px 50%"`.
|
77
|
+
*/
|
78
|
+
@property() snap?: string;
|
79
|
+
|
80
|
+
/** How close the divider must be to a snap point until snapping occurs. */
|
81
|
+
@property({ type: Number, attribute: 'snap-threshold' }) snapThreshold = 12;
|
82
|
+
|
83
|
+
connectedCallback() {
|
84
|
+
super.connectedCallback();
|
85
|
+
this.resizeObserver = new ResizeObserver(entries => this.handleResize(entries));
|
86
|
+
this.updateComplete.then(() => this.resizeObserver.observe(this));
|
87
|
+
|
88
|
+
this.detectSize();
|
89
|
+
this.cachedPositionInPixels = this.percentageToPixels(this.position);
|
90
|
+
}
|
91
|
+
|
92
|
+
disconnectedCallback() {
|
93
|
+
super.disconnectedCallback();
|
94
|
+
this.resizeObserver.unobserve(this);
|
95
|
+
}
|
96
|
+
|
97
|
+
private detectSize() {
|
98
|
+
const { width, height } = this.getBoundingClientRect();
|
99
|
+
this.size = this.vertical ? height : width;
|
100
|
+
}
|
101
|
+
|
102
|
+
private percentageToPixels(value: number) {
|
103
|
+
return this.size * (value / 100);
|
104
|
+
}
|
105
|
+
|
106
|
+
private pixelsToPercentage(value: number) {
|
107
|
+
return (value / this.size) * 100;
|
108
|
+
}
|
109
|
+
|
110
|
+
private handleDrag(event: PointerEvent) {
|
111
|
+
const isRtl = false;
|
112
|
+
|
113
|
+
if (this.disabled) {
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
|
117
|
+
// Prevent text selection when dragging
|
118
|
+
if (event.cancelable) {
|
119
|
+
event.preventDefault();
|
120
|
+
}
|
121
|
+
|
122
|
+
drag(this, {
|
123
|
+
onMove: (x, y) => {
|
124
|
+
let newPositionInPixels = this.vertical ? y : x;
|
125
|
+
|
126
|
+
// Flip for end panels
|
127
|
+
if (this.primary === 'end') {
|
128
|
+
newPositionInPixels = this.size - newPositionInPixels;
|
129
|
+
}
|
130
|
+
|
131
|
+
// Check snap points
|
132
|
+
if (this.snap) {
|
133
|
+
const snaps = this.snap.split(' ');
|
134
|
+
|
135
|
+
snaps.forEach(value => {
|
136
|
+
let snapPoint: number;
|
137
|
+
|
138
|
+
if (value.endsWith('%')) {
|
139
|
+
snapPoint = this.size * (parseFloat(value) / 100);
|
140
|
+
} else {
|
141
|
+
snapPoint = parseFloat(value);
|
142
|
+
}
|
143
|
+
|
144
|
+
if (isRtl && !this.vertical) {
|
145
|
+
snapPoint = this.size - snapPoint;
|
146
|
+
}
|
147
|
+
|
148
|
+
if (
|
149
|
+
newPositionInPixels >= snapPoint - this.snapThreshold &&
|
150
|
+
newPositionInPixels <= snapPoint + this.snapThreshold
|
151
|
+
) {
|
152
|
+
newPositionInPixels = snapPoint;
|
153
|
+
}
|
154
|
+
});
|
155
|
+
}
|
156
|
+
|
157
|
+
this.position = clamp(this.pixelsToPercentage(newPositionInPixels), 0, 100);
|
158
|
+
},
|
159
|
+
initialEvent: event
|
160
|
+
});
|
161
|
+
}
|
162
|
+
|
163
|
+
private handleKeyDown(event: KeyboardEvent) {
|
164
|
+
if (this.disabled) {
|
165
|
+
return;
|
166
|
+
}
|
167
|
+
|
168
|
+
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {
|
169
|
+
let newPosition = this.position;
|
170
|
+
const incr = (event.shiftKey ? 10 : 1) * (this.primary === 'end' ? -1 : 1);
|
171
|
+
|
172
|
+
event.preventDefault();
|
173
|
+
|
174
|
+
if ((event.key === 'ArrowLeft' && !this.vertical) || (event.key === 'ArrowUp' && this.vertical)) {
|
175
|
+
newPosition -= incr;
|
176
|
+
}
|
177
|
+
|
178
|
+
if ((event.key === 'ArrowRight' && !this.vertical) || (event.key === 'ArrowDown' && this.vertical)) {
|
179
|
+
newPosition += incr;
|
180
|
+
}
|
181
|
+
|
182
|
+
if (event.key === 'Home') {
|
183
|
+
newPosition = this.primary === 'end' ? 100 : 0;
|
184
|
+
}
|
185
|
+
|
186
|
+
if (event.key === 'End') {
|
187
|
+
newPosition = this.primary === 'end' ? 0 : 100;
|
188
|
+
}
|
189
|
+
|
190
|
+
this.position = clamp(newPosition, 0, 100);
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
private handleResize(entries: ResizeObserverEntry[]) {
|
195
|
+
const { width, height } = entries[0].contentRect;
|
196
|
+
this.size = this.vertical ? height : width;
|
197
|
+
|
198
|
+
// Resize when a primary panel is set
|
199
|
+
if (this.primary) {
|
200
|
+
this.position = this.pixelsToPercentage(this.cachedPositionInPixels);
|
201
|
+
}
|
202
|
+
}
|
203
|
+
|
204
|
+
@watch('position')
|
205
|
+
handlePositionChange() {
|
206
|
+
this.cachedPositionInPixels = this.percentageToPixels(this.position);
|
207
|
+
this.positionInPixels = this.percentageToPixels(this.position);
|
208
|
+
this.emit('nile-reposition');
|
209
|
+
}
|
210
|
+
|
211
|
+
@watch('positionInPixels')
|
212
|
+
handlePositionInPixelsChange() {
|
213
|
+
this.position = this.pixelsToPercentage(this.positionInPixels);
|
214
|
+
}
|
215
|
+
|
216
|
+
@watch('vertical')
|
217
|
+
handleVerticalChange() {
|
218
|
+
this.detectSize();
|
219
|
+
}
|
220
|
+
|
221
|
+
render() {
|
222
|
+
const gridTemplate = this.vertical ? 'gridTemplateRows' : 'gridTemplateColumns';
|
223
|
+
const gridTemplateAlt = this.vertical ? 'gridTemplateColumns' : 'gridTemplateRows';
|
224
|
+
const isRtl = false;
|
225
|
+
const primary = `
|
226
|
+
clamp(
|
227
|
+
0%,
|
228
|
+
clamp(
|
229
|
+
var(--min),
|
230
|
+
${this.position}% - var(--divider-width) / 2,
|
231
|
+
var(--max)
|
232
|
+
),
|
233
|
+
calc(100% - var(--divider-width))
|
234
|
+
)
|
235
|
+
`;
|
236
|
+
const secondary = 'auto';
|
237
|
+
|
238
|
+
if (this.primary === 'end') {
|
239
|
+
if (isRtl && !this.vertical) {
|
240
|
+
this.style[gridTemplate] = `${primary} var(--divider-width) ${secondary}`;
|
241
|
+
} else {
|
242
|
+
this.style[gridTemplate] = `${secondary} var(--divider-width) ${primary}`;
|
243
|
+
}
|
244
|
+
} else {
|
245
|
+
if (isRtl && !this.vertical) {
|
246
|
+
this.style[gridTemplate] = `${secondary} var(--divider-width) ${primary}`;
|
247
|
+
} else {
|
248
|
+
this.style[gridTemplate] = `${primary} var(--divider-width) ${secondary}`;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
// Unset the alt grid template property
|
253
|
+
this.style[gridTemplateAlt] = '';
|
254
|
+
|
255
|
+
return html`
|
256
|
+
<slot name="start" part="panel start" class="start"></slot>
|
257
|
+
|
258
|
+
<div
|
259
|
+
part="divider"
|
260
|
+
class="divider"
|
261
|
+
tabindex=${ifDefined(this.disabled ? undefined : '0')}
|
262
|
+
role="separator"
|
263
|
+
aria-valuenow=${this.position}
|
264
|
+
aria-valuemin="0"
|
265
|
+
aria-valuemax="100"
|
266
|
+
aria-label="resize"
|
267
|
+
@keydown=${this.handleKeyDown}
|
268
|
+
@mousedown=${this.handleDrag}
|
269
|
+
@touchstart=${this.handleDrag}
|
270
|
+
>
|
271
|
+
<slot name="divider"></slot>
|
272
|
+
</div>
|
273
|
+
|
274
|
+
<slot name="end" part="panel end" class="end"></slot>
|
275
|
+
`;
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
export default NileSplitPanel;
|
280
|
+
|
281
|
+
declare global {
|
282
|
+
interface HTMLElementTagNameMap {
|
283
|
+
'nile-split-panel': NileSplitPanel;
|
284
|
+
}
|
285
|
+
}
|