@excom/scroll-into-view 0.1.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/.rush/temp/chunked-rush-logs/scroll-into-view.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/scroll-into-view.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/scroll-into-view.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_docs/all.log +1 -0
- package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_docs/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +5 -0
- package/index.css +5 -0
- package/index.ts +17 -0
- package/package.json +45 -0
- package/rush-logs/scroll-into-view.apply-exports.cache.log +1 -0
- package/rush-logs/scroll-into-view.apply-exports.log +1 -0
- package/rush-logs/scroll-into-view.build_docs.cache.log +1 -0
- package/rush-logs/scroll-into-view.build_docs.log +1 -0
- package/rush-logs/scroll-into-view.build_package-metas.cache.log +1 -0
- package/rush-logs/scroll-into-view.build_package-metas.log +1 -0
- package/scroll-into-view.ts +112 -0
- package/src/scroll-into-view.css +23 -0
- package/support/custom-elements.json +189 -0
- package/support/demos/simple.html +12 -0
- package/support/demos/sticky-header.html +22 -0
- package/support/dist-docs/scroll-into-view.md +130 -0
- package/support/docs/README.md +40 -0
- package/support/package-meta.json +220 -0
- package/support/tests/scroll-into-view.test.ts +77 -0
- package/support/tests/scroll-options.test.ts +169 -0
- package/support/tests/simple.view.test.ts +21 -0
- package/support/tests/sticky-header.view.test.ts +27 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
package/config/rig.json
ADDED
package/index.css
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ScrollIntoView } from "./scroll-into-view";
|
|
2
|
+
|
|
3
|
+
ScrollIntoView.define();
|
|
4
|
+
|
|
5
|
+
export { ScrollIntoView };
|
|
6
|
+
|
|
7
|
+
type T_HTMLScrollIntoViewElement = typeof ScrollIntoView.CustomElement;
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLScrollIntoViewElement extends T_HTMLScrollIntoViewElement {}
|
|
10
|
+
interface Window {
|
|
11
|
+
HTMLScrollIntoViewElement: HTMLScrollIntoViewElement;
|
|
12
|
+
}
|
|
13
|
+
interface HTMLElementTagNameMap {
|
|
14
|
+
"scroll-into-view": HTMLScrollIntoViewElement;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export type { HTMLScrollIntoViewElement };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@excom/scroll-into-view",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "<scroll-into-view> custom element",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.13.0"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@excom/kit-scroller": "^0.1.0",
|
|
12
|
+
"@excom/neutron": "^0.1.0",
|
|
13
|
+
"@excom/kit-utils": "^0.1.0",
|
|
14
|
+
"@excom/listenable-element": "^0.1.0"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@excom/heft-rig": "^0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"url": "excom-dev/nucleus",
|
|
22
|
+
"directory": "packages/scroll-into-view"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/scroll-into-view/support/docs/README.md",
|
|
25
|
+
"bugs": "https://github.com/excom-dev/nucleus/issues",
|
|
26
|
+
"keywords": [
|
|
27
|
+
"scroll-into-view",
|
|
28
|
+
"neutron",
|
|
29
|
+
"custom-elements"
|
|
30
|
+
],
|
|
31
|
+
"excom": {
|
|
32
|
+
"packageType": "kit-element"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
|
|
36
|
+
"build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
|
|
37
|
+
"format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
|
|
38
|
+
"test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
|
|
39
|
+
"coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
|
|
40
|
+
"dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
|
|
41
|
+
"preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
|
|
42
|
+
"build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
|
|
43
|
+
"build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Caching has been disabled for this project's "apply-exports" command.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { scrollElementIntoView } from "@excom/kit-scroller";
|
|
2
|
+
import { selectOne } from "@excom/kit-utils";
|
|
3
|
+
import { isNumber } from "@excom/kit-utils";
|
|
4
|
+
import { ListenableElement } from "@excom/listenable-element";
|
|
5
|
+
import { Neutron, TokenList } from "@excom/neutron";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Scrolls an element into view, based on configuration. By default it
|
|
9
|
+
* scrolls itself into view as soon as it connects to the DOM; combine with
|
|
10
|
+
* the inherited `listen-for` attribute to scroll on clicks, custom events,
|
|
11
|
+
* or lifecycles instead.
|
|
12
|
+
*
|
|
13
|
+
* @summary Scroll an element into view — on connect, click, or any event.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <!-- Jump to a section on click -->
|
|
17
|
+
* <scroll-into-view target-ref="#pricing" listen-for="click">
|
|
18
|
+
* See pricing
|
|
19
|
+
* </scroll-into-view>
|
|
20
|
+
*/
|
|
21
|
+
export const ScrollIntoView = Neutron.compose([
|
|
22
|
+
ListenableElement,
|
|
23
|
+
Neutron({
|
|
24
|
+
tag: "scroll-into-view",
|
|
25
|
+
props: {
|
|
26
|
+
/**
|
|
27
|
+
* @option
|
|
28
|
+
* CSS selector of the element to scroll to. Unset scrolls this
|
|
29
|
+
* element itself.
|
|
30
|
+
* @values <CSS Selector>
|
|
31
|
+
*/
|
|
32
|
+
targetRef: String,
|
|
33
|
+
/**
|
|
34
|
+
* @option
|
|
35
|
+
* Two tokens `<inline> <block>` (x and y, respectively) controlling
|
|
36
|
+
* how the target aligns inside the scrollport. Each token is
|
|
37
|
+
* `start`, `center`, `end`, `nearest`, or `none` (skip alignment on
|
|
38
|
+
* that axis).
|
|
39
|
+
* @default nearest start
|
|
40
|
+
* @values start | center | end | nearest | none
|
|
41
|
+
*/
|
|
42
|
+
scrollAlign: {
|
|
43
|
+
type: TokenList,
|
|
44
|
+
isValid: (value: string) =>
|
|
45
|
+
["start", "center", "end", "nearest", "none"].includes(value),
|
|
46
|
+
defaultValue: () => ["nearest", "start"],
|
|
47
|
+
},
|
|
48
|
+
/**
|
|
49
|
+
* @option
|
|
50
|
+
* Two pixel offsets `<x> <y>` applied after alignment — e.g. leave
|
|
51
|
+
* room for a sticky header by using a negative `<y>`.
|
|
52
|
+
* @default 0 0
|
|
53
|
+
* @values <px> <px>
|
|
54
|
+
*/
|
|
55
|
+
scrollOffset: { type: TokenList, defaultValue: () => ["0", "0"] },
|
|
56
|
+
/**
|
|
57
|
+
* @option
|
|
58
|
+
* Scroll animation.
|
|
59
|
+
* @default auto
|
|
60
|
+
* @values auto | smooth | instant
|
|
61
|
+
*/
|
|
62
|
+
scrollBehavior: { type: String, defaultValue: () => "auto" },
|
|
63
|
+
/**
|
|
64
|
+
* @option
|
|
65
|
+
* Only scroll if the target isn't already fully visible.
|
|
66
|
+
*/
|
|
67
|
+
ifNeeded: Boolean,
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
])
|
|
71
|
+
.defineMethods({
|
|
72
|
+
actionHandler: (element) => {
|
|
73
|
+
const target = (
|
|
74
|
+
element.targetRef
|
|
75
|
+
? selectOne(element.targetRef, {
|
|
76
|
+
scope: element,
|
|
77
|
+
})
|
|
78
|
+
: element
|
|
79
|
+
) as HTMLElement;
|
|
80
|
+
requestAnimationFrame(() => {
|
|
81
|
+
// wait until after next paint to scroll
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
const scrollInline = element.scrollAlign[0] || "nearest";
|
|
84
|
+
const scrollBlock = element.scrollAlign[1] || "start";
|
|
85
|
+
const offsetX = Number(element.scrollOffset[0]);
|
|
86
|
+
const offsetY = Number(element.scrollOffset[1]);
|
|
87
|
+
scrollElementIntoView(target, {
|
|
88
|
+
block:
|
|
89
|
+
scrollBlock && scrollBlock !== "none"
|
|
90
|
+
? (scrollBlock as ScrollLogicalPosition)
|
|
91
|
+
: undefined,
|
|
92
|
+
inline:
|
|
93
|
+
scrollInline && scrollInline !== "none"
|
|
94
|
+
? (scrollInline as ScrollLogicalPosition)
|
|
95
|
+
: undefined,
|
|
96
|
+
offsetInline: isNumber(offsetX) ? offsetX : undefined,
|
|
97
|
+
offsetBlock: isNumber(offsetY) ? offsetY : undefined,
|
|
98
|
+
behavior: element.scrollBehavior as ScrollBehavior,
|
|
99
|
+
onlyIfNeeded: element.ifNeeded,
|
|
100
|
+
});
|
|
101
|
+
}, 0);
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
.onConnected(
|
|
106
|
+
({ listenFor, listenForLifecycle, isMoving }) =>
|
|
107
|
+
!isMoving &&
|
|
108
|
+
!listenFor?.length &&
|
|
109
|
+
!listenForLifecycle?.length && {
|
|
110
|
+
handleEvent: ["connected"],
|
|
111
|
+
}
|
|
112
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@import "@excom/listenable-element/src/index.css";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Styles for `<scroll-into-view>`.
|
|
5
|
+
* @element scroll-into-view
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* Aliases */
|
|
9
|
+
/**
|
|
10
|
+
* Apply `<scroll-into-view>` host styles (positioning + the inherited
|
|
11
|
+
* pointer cursor when clickable) to any element, without registering the
|
|
12
|
+
* custom element — e.g. server-rendered markup.
|
|
13
|
+
* @cssalias
|
|
14
|
+
*/
|
|
15
|
+
@custom-selector :--scroll-into-view scroll-into-view, .tag-scroll-into-view;
|
|
16
|
+
|
|
17
|
+
@define-mixin module-scroll-into-view {
|
|
18
|
+
:--scroll-into-view {
|
|
19
|
+
display: block;
|
|
20
|
+
position: relative;
|
|
21
|
+
@mixin listenable-element;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"modules": [
|
|
4
|
+
{
|
|
5
|
+
"kind": "javascript-module",
|
|
6
|
+
"path": "scroll-into-view.ts",
|
|
7
|
+
"declarations": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "class",
|
|
10
|
+
"name": "ScrollIntoView",
|
|
11
|
+
"customElement": true,
|
|
12
|
+
"tagName": "scroll-into-view",
|
|
13
|
+
"mixins": [
|
|
14
|
+
{
|
|
15
|
+
"name": "ListenableElement",
|
|
16
|
+
"package": "@excom/listenable-element"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"summary": "Scroll an element into view — on connect, click, or any event.",
|
|
20
|
+
"description": "Scrolls an element into view, based on configuration. By default it scrolls itself into view as soon as it connects to the DOM; combine with the inherited `listen-for` attribute to scroll on clicks, custom events, or lifecycles instead.",
|
|
21
|
+
"attributes": [
|
|
22
|
+
{
|
|
23
|
+
"name": "target-ref",
|
|
24
|
+
"type": {
|
|
25
|
+
"text": "string"
|
|
26
|
+
},
|
|
27
|
+
"description": "CSS selector of the element to scroll to. Unset scrolls this element itself.",
|
|
28
|
+
"fieldName": "targetRef",
|
|
29
|
+
"values": [
|
|
30
|
+
"<CSS Selector>"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "scroll-align",
|
|
35
|
+
"type": {
|
|
36
|
+
"text": "tokenlist"
|
|
37
|
+
},
|
|
38
|
+
"description": "Two tokens `<inline> <block>` (x and y, respectively) controlling how the target aligns inside the scrollport. Each token is `start`, `center`, `end`, `nearest`, or `none` (skip alignment on that axis).",
|
|
39
|
+
"fieldName": "scrollAlign",
|
|
40
|
+
"default": "nearest start",
|
|
41
|
+
"values": [
|
|
42
|
+
"start",
|
|
43
|
+
"center",
|
|
44
|
+
"end",
|
|
45
|
+
"nearest",
|
|
46
|
+
"none"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "scroll-offset",
|
|
51
|
+
"type": {
|
|
52
|
+
"text": "tokenlist"
|
|
53
|
+
},
|
|
54
|
+
"description": "Two pixel offsets `<x> <y>` applied after alignment — e.g. leave room for a sticky header by using a negative `<y>`.",
|
|
55
|
+
"fieldName": "scrollOffset",
|
|
56
|
+
"default": "0 0",
|
|
57
|
+
"values": [
|
|
58
|
+
"<px> <px>"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "scroll-behavior",
|
|
63
|
+
"type": {
|
|
64
|
+
"text": "string"
|
|
65
|
+
},
|
|
66
|
+
"description": "Scroll animation.",
|
|
67
|
+
"fieldName": "scrollBehavior",
|
|
68
|
+
"default": "auto",
|
|
69
|
+
"values": [
|
|
70
|
+
"auto",
|
|
71
|
+
"smooth",
|
|
72
|
+
"instant"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "if-needed",
|
|
77
|
+
"type": {
|
|
78
|
+
"text": "boolean"
|
|
79
|
+
},
|
|
80
|
+
"description": "Only scroll if the target isn't already fully visible.",
|
|
81
|
+
"fieldName": "ifNeeded"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"members": [
|
|
85
|
+
{
|
|
86
|
+
"kind": "field",
|
|
87
|
+
"name": "targetRef",
|
|
88
|
+
"type": {
|
|
89
|
+
"text": "string"
|
|
90
|
+
},
|
|
91
|
+
"privacy": "public",
|
|
92
|
+
"readonly": false,
|
|
93
|
+
"description": "CSS selector of the element to scroll to. Unset scrolls this element itself.",
|
|
94
|
+
"_neutron": {
|
|
95
|
+
"surface": "option"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"kind": "field",
|
|
100
|
+
"name": "scrollAlign",
|
|
101
|
+
"type": {
|
|
102
|
+
"text": "tokenlist"
|
|
103
|
+
},
|
|
104
|
+
"privacy": "public",
|
|
105
|
+
"readonly": false,
|
|
106
|
+
"description": "Two tokens `<inline> <block>` (x and y, respectively) controlling how the target aligns inside the scrollport. Each token is `start`, `center`, `end`, `nearest`, or `none` (skip alignment on that axis).",
|
|
107
|
+
"default": "nearest start",
|
|
108
|
+
"_neutron": {
|
|
109
|
+
"surface": "option"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"kind": "field",
|
|
114
|
+
"name": "scrollOffset",
|
|
115
|
+
"type": {
|
|
116
|
+
"text": "tokenlist"
|
|
117
|
+
},
|
|
118
|
+
"privacy": "public",
|
|
119
|
+
"readonly": false,
|
|
120
|
+
"description": "Two pixel offsets `<x> <y>` applied after alignment — e.g. leave room for a sticky header by using a negative `<y>`.",
|
|
121
|
+
"default": "0 0",
|
|
122
|
+
"_neutron": {
|
|
123
|
+
"surface": "option"
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"kind": "field",
|
|
128
|
+
"name": "scrollBehavior",
|
|
129
|
+
"type": {
|
|
130
|
+
"text": "string"
|
|
131
|
+
},
|
|
132
|
+
"privacy": "public",
|
|
133
|
+
"readonly": false,
|
|
134
|
+
"description": "Scroll animation.",
|
|
135
|
+
"default": "auto",
|
|
136
|
+
"_neutron": {
|
|
137
|
+
"surface": "option"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"kind": "field",
|
|
142
|
+
"name": "ifNeeded",
|
|
143
|
+
"type": {
|
|
144
|
+
"text": "boolean"
|
|
145
|
+
},
|
|
146
|
+
"privacy": "public",
|
|
147
|
+
"readonly": false,
|
|
148
|
+
"description": "Only scroll if the target isn't already fully visible.",
|
|
149
|
+
"_neutron": {
|
|
150
|
+
"surface": "option"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"_neutron": {
|
|
155
|
+
"cssAliases": [
|
|
156
|
+
{
|
|
157
|
+
"name": ":--scroll-into-view",
|
|
158
|
+
"selectors": [
|
|
159
|
+
"scroll-into-view",
|
|
160
|
+
".tag-scroll-into-view"
|
|
161
|
+
],
|
|
162
|
+
"kind": "element",
|
|
163
|
+
"description": "Apply `<scroll-into-view>` host styles (positioning + the inherited pointer cursor when clickable) to any element, without registering the custom element — e.g. server-rendered markup."
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
"exports": [
|
|
170
|
+
{
|
|
171
|
+
"kind": "js",
|
|
172
|
+
"name": "ScrollIntoView",
|
|
173
|
+
"declaration": {
|
|
174
|
+
"name": "ScrollIntoView",
|
|
175
|
+
"module": "scroll-into-view.ts"
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"kind": "custom-element-definition",
|
|
180
|
+
"name": "scroll-into-view",
|
|
181
|
+
"declaration": {
|
|
182
|
+
"name": "ScrollIntoView",
|
|
183
|
+
"module": "scroll-into-view.ts"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<div class="scroll-area">
|
|
3
|
+
<p>Keep scrolling…</p>
|
|
4
|
+
<p>Keep scrolling…</p>
|
|
5
|
+
<p>Keep scrolling…</p>
|
|
6
|
+
<scroll-into-view>
|
|
7
|
+
<p role="note">👋 Scrolled into view on connect — no attributes needed.</p>
|
|
8
|
+
</scroll-into-view>
|
|
9
|
+
<p>More content below.</p>
|
|
10
|
+
<p>More content below.</p>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<scroll-into-view role="button" target-ref="#section-3" listen-for="click"
|
|
3
|
+
scroll-offset="0 -50" scroll-behavior="smooth">
|
|
4
|
+
Jump to Section 3
|
|
5
|
+
</scroll-into-view>
|
|
6
|
+
<div class="scroll-area">
|
|
7
|
+
<header>Sticky header</header>
|
|
8
|
+
<section id="section-1">
|
|
9
|
+
<h4>Section 1</h4>
|
|
10
|
+
<p>The target section is further down.</p>
|
|
11
|
+
</section>
|
|
12
|
+
<section id="section-2">
|
|
13
|
+
<h4>Section 2</h4>
|
|
14
|
+
<p>Keep scrolling…</p>
|
|
15
|
+
</section>
|
|
16
|
+
<section id="section-3">
|
|
17
|
+
<h4>Section 3</h4>
|
|
18
|
+
<p>The sticky header would normally cover this —
|
|
19
|
+
<code>scroll-offset="0 -50"</code> leaves room for it.</p>
|
|
20
|
+
</section>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# scroll-into-view
|
|
2
|
+
|
|
3
|
+
Scrolls an element into view — on connect by default, or on click / any event via the inherited `listen-for`.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
```html
|
|
7
|
+
<div>
|
|
8
|
+
<div class="scroll-area">
|
|
9
|
+
<p>Keep scrolling…</p>
|
|
10
|
+
<p>Keep scrolling…</p>
|
|
11
|
+
<p>Keep scrolling…</p>
|
|
12
|
+
<scroll-into-view>
|
|
13
|
+
<p role="note">👋 Scrolled into view on connect — no attributes needed.</p>
|
|
14
|
+
</scroll-into-view>
|
|
15
|
+
<p>More content below.</p>
|
|
16
|
+
<p>More content below.</p>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Scroll on connect** Works with no attributes needed
|
|
25
|
+
- **Any trigger** Combine with `listen-for` to scroll on click, custom events, or lifecycles
|
|
26
|
+
- **Alignment control** `scroll-align` picks start / center / end / nearest per axis
|
|
27
|
+
- **Offset for fixed headers** `scroll-offset` nudges the final position after alignment
|
|
28
|
+
- **Skip redundant scrolls** `if-needed` scrolls only when the target isn't already visible
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
`@excom/scroll-into-view` v0.1.0
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm add @excom/scroll-into-view
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @excom/scroll-into-view
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
yarn add @excom/scroll-into-view
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Import
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import "@excom/scroll-into-view";
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
By default, `<scroll-into-view>` scrolls *itself* into view as soon as it connects to the DOM. Set `target-ref` to scroll a different element instead, and pair with the inherited `listen-for` to trigger on click or any event rather than on connect.
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<!-- Jump to a section on click -->
|
|
61
|
+
<scroll-into-view target-ref="#pricing" listen-for="click">
|
|
62
|
+
See pricing
|
|
63
|
+
</scroll-into-view>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### API Reference
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
#### Attributes
|
|
70
|
+
|
|
71
|
+
| Name | Surface | Type | Default | Values | Description | Inherited from |
|
|
72
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
73
|
+
| `target-ref` | option | `string` | | `<CSS Selector>` | CSS selector of the element to scroll to. Unset scrolls this element itself. | |
|
|
74
|
+
| `scroll-align` | option | `tokenlist` | `"nearest start"` | `"start"` \| `"center"` \| `"end"` \| `"nearest"` \| `"none"` | Two tokens `<inline> <block>` (x and y, respectively) controlling how the target aligns inside the scrollport. Each token is `start`, `center`, `end`, `nearest`, or `none` (skip alignment on that axis). | |
|
|
75
|
+
| `scroll-offset` | option | `tokenlist` | `"0 0"` | `<px> <px>` | Two pixel offsets `<x> <y>` applied after alignment — e.g. leave room for a sticky header by using a negative `<y>`. | |
|
|
76
|
+
| `scroll-behavior` | option | `string` | `"auto"` | `"auto"` \| `"smooth"` \| `"instant"` | Scroll animation. | |
|
|
77
|
+
| `if-needed` | option | `boolean` | | | Only scroll if the target isn't already fully visible. | |
|
|
78
|
+
| `host-ref` | option | `string` | | `<CSS Selector>` \| `"window"` \| `"document"` \| `"html"` \| `"body"` \| `"head"` | Listen on another element / `window` / `document` — e.g. Escape to dismiss a dialog from a global `keydown`. Defaults to `:scope`. Used with `listen-for`. Not compatible with `listen-for-lifecycle`. The selector MUST resolve when `host-ref` is set — it will not wait for a match to appear. | `@excom/listenable-element` |
|
|
79
|
+
| `listen-for` | option | `tokenlist` | | `<EventName>…` | Space-separated event names to listen for. Defaults to `click` when unset (and no lifecycle list is set). | `@excom/listenable-element` |
|
|
80
|
+
| `listen-for-lifecycle` | option | `tokenlist` | | `"connected"` \| `"disconnected"` \| `"adopted"` | Space-separated element lifecycles to handle. | `@excom/listenable-element` |
|
|
81
|
+
| `listen-once` | option | `boolean` | | | Handle each distinct event name / lifecycle at most once. | `@excom/listenable-element` |
|
|
82
|
+
| `selector-filter` | option | `string` | | `<CSS Selector>` | Only handle events whose `event.target` matches this CSS selector. Does not support `:scope` in the selector. | `@excom/listenable-element` |
|
|
83
|
+
| `keycode-filter` | option | `tokenlist` | | `<key` \| `mod+key>…` | Space-separated key filters (OR). Join modifiers with `+` (AND, any order): `shift+k tab` → Shift+K or Tab. Modifiers: `shift`, `alt`, `ctrl`/`control`, `meta`. Case-insensitive. | `@excom/listenable-element` |
|
|
84
|
+
| `pathname-filter` | option | `tokenlist` | | `<pathname>…` | Only handle when `location.pathname` is one of these values — route-aware behaviors without a separate router element. | `@excom/listenable-element` |
|
|
85
|
+
| `prevent-default` | option | `boolean` | | | Call `preventDefault()` on matched events (ignored for lifecycles). | `@excom/listenable-element` |
|
|
86
|
+
| `stop-propagation` | option | `boolean` | | | Call `stopPropagation()` on matched events (ignored for lifecycles). | `@excom/listenable-element` |
|
|
87
|
+
| `stop-immediate-propagation` | option | `boolean` | | | Call `stopImmediatePropagation()` on matched events (ignored for lifecycles). | `@excom/listenable-element` |
|
|
88
|
+
| `vibrate-ms` | option | `number` | `"20 (when attribute is present with no value)"` | | Vibrate on handle (`navigator.vibrate`). Empty / `0` uses a 20ms pulse. | `@excom/listenable-element` |
|
|
89
|
+
| `delay-ms` | option | `number` | | | Delay handling by this many milliseconds. | `@excom/listenable-element` |
|
|
90
|
+
| `is-debounced` | option | `boolean` | | | With `delay-ms`, coalesce bursts into one trailing call (debounce). | `@excom/listenable-element` |
|
|
91
|
+
|
|
92
|
+
#### CSS Aliases
|
|
93
|
+
|
|
94
|
+
| Alias | Kind | Matches | Description |
|
|
95
|
+
| --- | --- | --- | --- |
|
|
96
|
+
| `:--scroll-into-view` | element | `scroll-into-view`, `.tag-scroll-into-view` | Apply `<scroll-into-view>` host styles (positioning + the inherited pointer cursor when clickable) to any element, without registering the custom element — e.g. server-rendered markup. |
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
### Examples
|
|
101
|
+
|
|
102
|
+
#### Offset for a sticky header
|
|
103
|
+
|
|
104
|
+
`scroll-offset="0 -50"` (negative `<y>`) leaves room for a sticky header after alignment; `scroll-behavior="smooth"` animates the scroll. This is the pattern used to jump between sections without the header covering the target's heading.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<div>
|
|
109
|
+
<scroll-into-view role="button" target-ref="#section-3" listen-for="click"
|
|
110
|
+
scroll-offset="0 -50" scroll-behavior="smooth">
|
|
111
|
+
Jump to Section 3
|
|
112
|
+
</scroll-into-view>
|
|
113
|
+
<div class="scroll-area">
|
|
114
|
+
<header>Sticky header</header>
|
|
115
|
+
<section id="section-1">
|
|
116
|
+
<h4>Section 1</h4>
|
|
117
|
+
<p>The target section is further down.</p>
|
|
118
|
+
</section>
|
|
119
|
+
<section id="section-2">
|
|
120
|
+
<h4>Section 2</h4>
|
|
121
|
+
<p>Keep scrolling…</p>
|
|
122
|
+
</section>
|
|
123
|
+
<section id="section-3">
|
|
124
|
+
<h4>Section 3</h4>
|
|
125
|
+
<p>The sticky header would normally cover this —
|
|
126
|
+
<code>scroll-offset="0 -50"</code> leaves room for it.</p>
|
|
127
|
+
</section>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# scroll-into-view
|
|
2
|
+
|
|
3
|
+
Scrolls an element into view — on connect by default, or on click / any event via the inherited `listen-for`.
|
|
4
|
+
|
|
5
|
+
<include-content data-demo="simple"></include-content>
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Scroll on connect** Works with no attributes needed
|
|
10
|
+
- **Any trigger** Combine with `listen-for` to scroll on click, custom events, or lifecycles
|
|
11
|
+
- **Alignment control** `scroll-align` picks start / center / end / nearest per axis
|
|
12
|
+
- **Offset for fixed headers** `scroll-offset` nudges the final position after alignment
|
|
13
|
+
- **Skip redundant scrolls** `if-needed` scrolls only when the target isn't already visible
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
<include-content is-active template-ref="/views/install-section/install-section.html"></include-content>
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
By default, `<scroll-into-view>` scrolls *itself* into view as soon as it connects to the DOM. Set `target-ref` to scroll a different element instead, and pair with the inherited `listen-for` to trigger on click or any event rather than on connect.
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<!-- Jump to a section on click -->
|
|
25
|
+
<scroll-into-view target-ref="#pricing" listen-for="click">
|
|
26
|
+
See pricing
|
|
27
|
+
</scroll-into-view>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### API Reference
|
|
31
|
+
|
|
32
|
+
<include-content is-active template-ref="/views/api-reference/api-reference.html"></include-content>
|
|
33
|
+
|
|
34
|
+
### Examples
|
|
35
|
+
|
|
36
|
+
#### Offset for a sticky header
|
|
37
|
+
|
|
38
|
+
`scroll-offset="0 -50"` (negative `<y>`) leaves room for a sticky header after alignment; `scroll-behavior="smooth"` animates the scroll. This is the pattern used to jump between sections without the header covering the target's heading.
|
|
39
|
+
|
|
40
|
+
<include-content data-demo="sticky-header"></include-content>
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
{
|
|
2
|
+
"shortName": "scroll-into-view",
|
|
3
|
+
"package": {
|
|
4
|
+
"name": "@excom/scroll-into-view",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "<scroll-into-view> custom element",
|
|
7
|
+
"peerDependencies": {},
|
|
8
|
+
"excom": {
|
|
9
|
+
"packageType": "kit-element"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"demos": {
|
|
13
|
+
"simple": "<div>\n <div class=\"scroll-area\">\n <p>Keep scrolling…</p>\n <p>Keep scrolling…</p>\n <p>Keep scrolling…</p>\n <scroll-into-view>\n <p role=\"note\">👋 Scrolled into view on connect — no attributes needed.</p>\n </scroll-into-view>\n <p>More content below.</p>\n <p>More content below.</p>\n </div>\n</div>\n",
|
|
14
|
+
"sticky-header": "<div>\n <scroll-into-view role=\"button\" target-ref=\"#section-3\" listen-for=\"click\"\n scroll-offset=\"0 -50\" scroll-behavior=\"smooth\">\n Jump to Section 3\n </scroll-into-view>\n <div class=\"scroll-area\">\n <header>Sticky header</header>\n <section id=\"section-1\">\n <h4>Section 1</h4>\n <p>The target section is further down.</p>\n </section>\n <section id=\"section-2\">\n <h4>Section 2</h4>\n <p>Keep scrolling…</p>\n </section>\n <section id=\"section-3\">\n <h4>Section 3</h4>\n <p>The sticky header would normally cover this —\n <code>scroll-offset=\"0 -50\"</code> leaves room for it.</p>\n </section>\n </div>\n</div>\n"
|
|
15
|
+
},
|
|
16
|
+
"readme": "<h1 id=\"md-scroll-into-view\">scroll-into-view</h1>\n<p>Scrolls an element into view — on connect by default, or on click / any event via the inherited <code>listen-for</code>.</p>\n<p><include-content data-demo=\"simple\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Scroll on connect</strong> Works with no attributes needed</li>\n<li><strong>Any trigger</strong> Combine with <code>listen-for</code> to scroll on click, custom events, or lifecycles</li>\n<li><strong>Alignment control</strong> <code>scroll-align</code> picks start / center / end / nearest per axis</li>\n<li><strong>Offset for fixed headers</strong> <code>scroll-offset</code> nudges the final position after alignment</li>\n<li><strong>Skip redundant scrolls</strong> <code>if-needed</code> scrolls only when the target isn't already visible</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<p>By default, <code><scroll-into-view></code> scrolls <em>itself</em> into view as soon as it connects to the DOM. Set <code>target-ref</code> to scroll a different element instead, and pair with the inherited <code>listen-for</code> to trigger on click or any event rather than on connect.</p>\n<include-content data-language=\"html\"><template><!-- Jump to a section on click -->\n<scroll-into-view target-ref=\"#pricing\" listen-for=\"click\">\n See pricing\n</scroll-into-view></template></include-content>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-offset-for-a-sticky-header\">Offset for a sticky header</h4>\n<p><code>scroll-offset="0 -50"</code> (negative <code><y></code>) leaves room for a sticky header after alignment; <code>scroll-behavior="smooth"</code> animates the scroll. This is the pattern used to jump between sections without the header covering the target's heading.</p>\n<p><include-content data-demo=\"sticky-header\"></include-content></p>\n",
|
|
17
|
+
"docs": {
|
|
18
|
+
"readme": "<h1 id=\"md-scroll-into-view\">scroll-into-view</h1>\n<p>Scrolls an element into view — on connect by default, or on click / any event via the inherited <code>listen-for</code>.</p>\n<p><include-content data-demo=\"simple\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Scroll on connect</strong> Works with no attributes needed</li>\n<li><strong>Any trigger</strong> Combine with <code>listen-for</code> to scroll on click, custom events, or lifecycles</li>\n<li><strong>Alignment control</strong> <code>scroll-align</code> picks start / center / end / nearest per axis</li>\n<li><strong>Offset for fixed headers</strong> <code>scroll-offset</code> nudges the final position after alignment</li>\n<li><strong>Skip redundant scrolls</strong> <code>if-needed</code> scrolls only when the target isn't already visible</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<p>By default, <code><scroll-into-view></code> scrolls <em>itself</em> into view as soon as it connects to the DOM. Set <code>target-ref</code> to scroll a different element instead, and pair with the inherited <code>listen-for</code> to trigger on click or any event rather than on connect.</p>\n<include-content data-language=\"html\"><template><!-- Jump to a section on click -->\n<scroll-into-view target-ref=\"#pricing\" listen-for=\"click\">\n See pricing\n</scroll-into-view></template></include-content>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-offset-for-a-sticky-header\">Offset for a sticky header</h4>\n<p><code>scroll-offset="0 -50"</code> (negative <code><y></code>) leaves room for a sticky header after alignment; <code>scroll-behavior="smooth"</code> animates the scroll. This is the pattern used to jump between sections without the header covering the target's heading.</p>\n<p><include-content data-demo=\"sticky-header\"></include-content></p>\n"
|
|
19
|
+
},
|
|
20
|
+
"installation": {
|
|
21
|
+
"name": "@excom/scroll-into-view",
|
|
22
|
+
"shortName": "scroll-into-view",
|
|
23
|
+
"version": "0.1.0",
|
|
24
|
+
"description": "<scroll-into-view> custom element",
|
|
25
|
+
"packageType": "kit-element",
|
|
26
|
+
"cdn": "<script src=\"https://unpkg.com/@excom/kit-utils/dist/index.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@excom/neutron/dist/index.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@excom/scroll-into-view@0.1.0/dist/index.umd.min.js\"></script>\n<link rel=\"stylesheet\" href=\"https://unpkg.com/@excom/scroll-into-view@0.1.0/dist/index.css\">",
|
|
27
|
+
"install": {
|
|
28
|
+
"npm": "npm install @excom/scroll-into-view"
|
|
29
|
+
},
|
|
30
|
+
"imports": {
|
|
31
|
+
"js": "import \"@excom/scroll-into-view\";",
|
|
32
|
+
"css": "@import \"@excom/scroll-into-view\";",
|
|
33
|
+
"html": "<!-- import path to `node_modules` will depend on your build setup -->\n<script type=\"module\" src=\"/node_modules/@excom/scroll-into-view\"></script>\n<link rel=\"stylesheet\" href=\"/node_modules/@excom/scroll-into-view\">"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": []
|
|
36
|
+
},
|
|
37
|
+
"elementApis": [
|
|
38
|
+
{
|
|
39
|
+
"tag": "scroll-into-view",
|
|
40
|
+
"summary": "Scroll an element into view — on connect, click, or any event.",
|
|
41
|
+
"kind": "class",
|
|
42
|
+
"attributes": [
|
|
43
|
+
{
|
|
44
|
+
"name": "delay-ms",
|
|
45
|
+
"type": "number",
|
|
46
|
+
"description": "Delay handling by this many milliseconds.",
|
|
47
|
+
"fieldName": "delayMs",
|
|
48
|
+
"surface": "option",
|
|
49
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "host-ref",
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "Listen on another element / <code>window</code> / <code>document</code> — e.g. Escape to dismiss a dialog from a global <code>keydown</code>. Defaults to <code>:scope</code>. Used with <code>listen-for</code>. Not compatible with <code>listen-for-lifecycle</code>. The selector MUST resolve when <code>host-ref</code> is set — it will not wait for a match to appear.",
|
|
55
|
+
"fieldName": "hostRef",
|
|
56
|
+
"surface": "option",
|
|
57
|
+
"values": "<CSS Selector> | \"window\" | \"document\" | \"html\" | \"body\" | \"head\"",
|
|
58
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "if-needed",
|
|
62
|
+
"type": "boolean",
|
|
63
|
+
"description": "Only scroll if the target isn't already fully visible.",
|
|
64
|
+
"fieldName": "ifNeeded",
|
|
65
|
+
"surface": "option"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "is-debounced",
|
|
69
|
+
"type": "boolean",
|
|
70
|
+
"description": "With <code>delay-ms</code>, coalesce bursts into one trailing call (debounce).",
|
|
71
|
+
"fieldName": "isDebounced",
|
|
72
|
+
"surface": "option",
|
|
73
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "keycode-filter",
|
|
77
|
+
"type": "tokenlist",
|
|
78
|
+
"description": "Space-separated key filters (OR). Join modifiers with <code>+</code> (AND, any order): <code>shift+k tab</code> → Shift+K or Tab. Modifiers: <code>shift</code>, <code>alt</code>, <code>ctrl</code>/<code>control</code>, <code>meta</code>. Case-insensitive.",
|
|
79
|
+
"fieldName": "keycodeFilter",
|
|
80
|
+
"surface": "option",
|
|
81
|
+
"values": "<key | mod+key>…",
|
|
82
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "listen-for",
|
|
86
|
+
"type": "tokenlist",
|
|
87
|
+
"description": "Space-separated event names to listen for. Defaults to <code>click</code> when unset (and no lifecycle list is set).",
|
|
88
|
+
"fieldName": "listenFor",
|
|
89
|
+
"surface": "option",
|
|
90
|
+
"values": "<EventName>…",
|
|
91
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "listen-for-lifecycle",
|
|
95
|
+
"type": "tokenlist",
|
|
96
|
+
"description": "Space-separated element lifecycles to handle.",
|
|
97
|
+
"fieldName": "listenForLifecycle",
|
|
98
|
+
"surface": "option",
|
|
99
|
+
"values": "\"connected\" | \"disconnected\" | \"adopted\"",
|
|
100
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "listen-once",
|
|
104
|
+
"type": "boolean",
|
|
105
|
+
"description": "Handle each distinct event name / lifecycle at most once.",
|
|
106
|
+
"fieldName": "listenOnce",
|
|
107
|
+
"surface": "option",
|
|
108
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "pathname-filter",
|
|
112
|
+
"type": "tokenlist",
|
|
113
|
+
"description": "Only handle when <code>location.pathname</code> is one of these values — route-aware behaviors without a separate router element.",
|
|
114
|
+
"fieldName": "pathnameFilter",
|
|
115
|
+
"surface": "option",
|
|
116
|
+
"values": "<pathname>…",
|
|
117
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "prevent-default",
|
|
121
|
+
"type": "boolean",
|
|
122
|
+
"description": "Call <code>preventDefault()</code> on matched events (ignored for lifecycles).",
|
|
123
|
+
"fieldName": "preventDefault",
|
|
124
|
+
"surface": "option",
|
|
125
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "scroll-align",
|
|
129
|
+
"type": "tokenlist",
|
|
130
|
+
"description": "Two tokens <code><inline> <block></code> (x and y, respectively) controlling how the target aligns inside the scrollport. Each token is <code>start</code>, <code>center</code>, <code>end</code>, <code>nearest</code>, or <code>none</code> (skip alignment on that axis).",
|
|
131
|
+
"fieldName": "scrollAlign",
|
|
132
|
+
"surface": "option",
|
|
133
|
+
"default": "\"nearest start\"",
|
|
134
|
+
"values": "\"start\" | \"center\" | \"end\" | \"nearest\" | \"none\""
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "scroll-behavior",
|
|
138
|
+
"type": "string",
|
|
139
|
+
"description": "Scroll animation.",
|
|
140
|
+
"fieldName": "scrollBehavior",
|
|
141
|
+
"surface": "option",
|
|
142
|
+
"default": "\"auto\"",
|
|
143
|
+
"values": "\"auto\" | \"smooth\" | \"instant\""
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "scroll-offset",
|
|
147
|
+
"type": "tokenlist",
|
|
148
|
+
"description": "Two pixel offsets <code><x> <y></code> applied after alignment — e.g. leave room for a sticky header by using a negative <code><y></code>.",
|
|
149
|
+
"fieldName": "scrollOffset",
|
|
150
|
+
"surface": "option",
|
|
151
|
+
"default": "\"0 0\"",
|
|
152
|
+
"values": "<px> <px>"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "selector-filter",
|
|
156
|
+
"type": "string",
|
|
157
|
+
"description": "Only handle events whose <code>event.target</code> matches this CSS selector. Does not support <code>:scope</code> in the selector.",
|
|
158
|
+
"fieldName": "selectorFilter",
|
|
159
|
+
"surface": "option",
|
|
160
|
+
"values": "<CSS Selector>",
|
|
161
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "stop-immediate-propagation",
|
|
165
|
+
"type": "boolean",
|
|
166
|
+
"description": "Call <code>stopImmediatePropagation()</code> on matched events (ignored for lifecycles).",
|
|
167
|
+
"fieldName": "stopImmediatePropagation",
|
|
168
|
+
"surface": "option",
|
|
169
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"name": "stop-propagation",
|
|
173
|
+
"type": "boolean",
|
|
174
|
+
"description": "Call <code>stopPropagation()</code> on matched events (ignored for lifecycles).",
|
|
175
|
+
"fieldName": "stopPropagation",
|
|
176
|
+
"surface": "option",
|
|
177
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "target-ref",
|
|
181
|
+
"type": "string",
|
|
182
|
+
"description": "CSS selector of the element to scroll to. Unset scrolls this element itself.",
|
|
183
|
+
"fieldName": "targetRef",
|
|
184
|
+
"surface": "option",
|
|
185
|
+
"values": "<CSS Selector>"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"name": "vibrate-ms",
|
|
189
|
+
"type": "number",
|
|
190
|
+
"description": "Vibrate on handle (<code>navigator.vibrate</code>). Empty / <code>0</code> uses a 20ms pulse.",
|
|
191
|
+
"fieldName": "vibrateMs",
|
|
192
|
+
"surface": "option",
|
|
193
|
+
"default": "\"20 (when attribute is present with no value)\"",
|
|
194
|
+
"inheritedFrom": "@excom/listenable-element"
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
"events": [],
|
|
198
|
+
"slots": [],
|
|
199
|
+
"cssProperties": [],
|
|
200
|
+
"cssClasses": [],
|
|
201
|
+
"cssAliases": [
|
|
202
|
+
{
|
|
203
|
+
"name": ":--scroll-into-view",
|
|
204
|
+
"selectors": [
|
|
205
|
+
"scroll-into-view",
|
|
206
|
+
".tag-scroll-into-view"
|
|
207
|
+
],
|
|
208
|
+
"kind": "element",
|
|
209
|
+
"description": "Apply <code><scroll-into-view></code> host styles (positioning + the inherited pointer cursor when clickable) to any element, without registering the custom element — e.g. server-rendered markup."
|
|
210
|
+
}
|
|
211
|
+
],
|
|
212
|
+
"listens": [],
|
|
213
|
+
"commands": [],
|
|
214
|
+
"defaultActions": [],
|
|
215
|
+
"expectedChildren": [],
|
|
216
|
+
"provisions": []
|
|
217
|
+
}
|
|
218
|
+
],
|
|
219
|
+
"exportedFiles": {}
|
|
220
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
describe,
|
|
4
|
+
expect,
|
|
5
|
+
fixture,
|
|
6
|
+
it,
|
|
7
|
+
vi,
|
|
8
|
+
wait,
|
|
9
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
10
|
+
import "../../index";
|
|
11
|
+
|
|
12
|
+
describe("scroll-into-view", () => {
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
document.body.innerHTML = "";
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("invokes scroll on connect when no triggers configured", async () => {
|
|
19
|
+
const scrollToSpy = vi
|
|
20
|
+
.spyOn(window, "scrollTo")
|
|
21
|
+
.mockImplementation(() => {});
|
|
22
|
+
fixture<HTMLScrollIntoViewElement>(
|
|
23
|
+
`<scroll-into-view></scroll-into-view>`,
|
|
24
|
+
);
|
|
25
|
+
await wait(50);
|
|
26
|
+
expect(scrollToSpy).toHaveBeenCalled();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("scrolls target element when targetRef is set", async () => {
|
|
30
|
+
const target = document.createElement("div");
|
|
31
|
+
target.id = "scroll-target";
|
|
32
|
+
document.body.appendChild(target);
|
|
33
|
+
const scrollToSpy = vi
|
|
34
|
+
.spyOn(window, "scrollTo")
|
|
35
|
+
.mockImplementation(() => {});
|
|
36
|
+
fixture<HTMLScrollIntoViewElement>(
|
|
37
|
+
`<scroll-into-view target-ref="#scroll-target"></scroll-into-view>`,
|
|
38
|
+
);
|
|
39
|
+
await wait(50);
|
|
40
|
+
expect(scrollToSpy).toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("defaults scrollAlign to nearest/start", () => {
|
|
44
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
45
|
+
`<scroll-into-view></scroll-into-view>`,
|
|
46
|
+
);
|
|
47
|
+
expect(el.scrollAlign).toEqual(["nearest", "start"]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("defaults scrollBehavior to auto", () => {
|
|
51
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
52
|
+
`<scroll-into-view></scroll-into-view>`,
|
|
53
|
+
);
|
|
54
|
+
expect(el.scrollBehavior).toBe("auto");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("defaults scrollOffset to 0 0", () => {
|
|
58
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
59
|
+
`<scroll-into-view></scroll-into-view>`,
|
|
60
|
+
);
|
|
61
|
+
expect(el.scrollOffset).toEqual(["0", "0"]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("accepts custom scroll-align", () => {
|
|
65
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
66
|
+
`<scroll-into-view scroll-align="center end"></scroll-into-view>`,
|
|
67
|
+
);
|
|
68
|
+
expect(el.scrollAlign).toEqual(["center", "end"]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("accepts custom scroll-offset", () => {
|
|
72
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
73
|
+
`<scroll-into-view scroll-offset="10 20"></scroll-into-view>`,
|
|
74
|
+
);
|
|
75
|
+
expect(el.scrollOffset).toEqual(["10", "20"]);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
fixture,
|
|
7
|
+
it,
|
|
8
|
+
wait,
|
|
9
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
10
|
+
// `vi` must come from "vitest" itself so `vi.mock` is hoisted with it
|
|
11
|
+
import { vi } from "vitest";
|
|
12
|
+
import { scrollElementIntoView } from "@excom/kit-scroller";
|
|
13
|
+
import "../../index";
|
|
14
|
+
|
|
15
|
+
// Intercept the scroller so the options `<scroll-into-view>` derives from its
|
|
16
|
+
// attributes can be asserted directly (layout is meaningless in the test DOM).
|
|
17
|
+
vi.mock("@excom/kit-scroller", async (importOriginal) => {
|
|
18
|
+
const original =
|
|
19
|
+
await importOriginal<typeof import("@excom/kit-scroller")>();
|
|
20
|
+
return { ...original, scrollElementIntoView: vi.fn(() => ({})) };
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const scrollMock = vi.mocked(scrollElementIntoView);
|
|
24
|
+
|
|
25
|
+
// actionHandler waits for a frame and then a macrotask before scrolling
|
|
26
|
+
const settle = () => wait(50);
|
|
27
|
+
|
|
28
|
+
describe("scroll-into-view (option parsing)", () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
scrollMock.mockClear();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
document.body.innerHTML = "";
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("passes the default alignment, offsets and behavior", async () => {
|
|
38
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
39
|
+
`<scroll-into-view></scroll-into-view>`,
|
|
40
|
+
);
|
|
41
|
+
await settle();
|
|
42
|
+
expect(scrollMock).toHaveBeenCalledTimes(1);
|
|
43
|
+
expect(scrollMock).toHaveBeenCalledWith(el, {
|
|
44
|
+
block: "start",
|
|
45
|
+
inline: "nearest",
|
|
46
|
+
offsetInline: 0,
|
|
47
|
+
offsetBlock: 0,
|
|
48
|
+
behavior: "auto",
|
|
49
|
+
onlyIfNeeded: false,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("maps scroll-align, scroll-offset, scroll-behavior and if-needed", async () => {
|
|
54
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
55
|
+
`<scroll-into-view scroll-align="center end" scroll-offset="10 -20" scroll-behavior="smooth" if-needed></scroll-into-view>`,
|
|
56
|
+
);
|
|
57
|
+
await settle();
|
|
58
|
+
expect(scrollMock).toHaveBeenCalledWith(el, {
|
|
59
|
+
block: "end",
|
|
60
|
+
inline: "center",
|
|
61
|
+
offsetInline: 10,
|
|
62
|
+
offsetBlock: -20,
|
|
63
|
+
behavior: "smooth",
|
|
64
|
+
onlyIfNeeded: true,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("skips an axis whose alignment token is `none`", async () => {
|
|
69
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
70
|
+
`<scroll-into-view scroll-align="none none"></scroll-into-view>`,
|
|
71
|
+
);
|
|
72
|
+
await settle();
|
|
73
|
+
expect(scrollMock).toHaveBeenCalledWith(el, {
|
|
74
|
+
block: undefined,
|
|
75
|
+
inline: undefined,
|
|
76
|
+
offsetInline: 0,
|
|
77
|
+
offsetBlock: 0,
|
|
78
|
+
behavior: "auto",
|
|
79
|
+
onlyIfNeeded: false,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("falls back to nearest / start for missing alignment tokens", async () => {
|
|
84
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
85
|
+
`<scroll-into-view scroll-align="end"></scroll-into-view>`,
|
|
86
|
+
);
|
|
87
|
+
expect(el.scrollAlign).toEqual(["end"]);
|
|
88
|
+
await settle();
|
|
89
|
+
expect(scrollMock).toHaveBeenLastCalledWith(
|
|
90
|
+
el,
|
|
91
|
+
expect.objectContaining({ inline: "end", block: "start" }),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
scrollMock.mockClear();
|
|
95
|
+
el.scrollAlign = [];
|
|
96
|
+
el.actionHandler();
|
|
97
|
+
await settle();
|
|
98
|
+
expect(scrollMock).toHaveBeenLastCalledWith(
|
|
99
|
+
el,
|
|
100
|
+
expect.objectContaining({ inline: "nearest", block: "start" }),
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("drops non-numeric offsets", async () => {
|
|
105
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
106
|
+
`<scroll-into-view scroll-offset="abc xyz"></scroll-into-view>`,
|
|
107
|
+
);
|
|
108
|
+
await settle();
|
|
109
|
+
expect(scrollMock).toHaveBeenCalledWith(
|
|
110
|
+
el,
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
offsetInline: undefined,
|
|
113
|
+
offsetBlock: undefined,
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("resolves target-ref relative to itself", async () => {
|
|
119
|
+
const wrapper = fixture<HTMLDivElement>(
|
|
120
|
+
`<div>
|
|
121
|
+
<p id="scroll-target-a">a</p>
|
|
122
|
+
<scroll-into-view target-ref="#scroll-target-a"></scroll-into-view>
|
|
123
|
+
</div>`,
|
|
124
|
+
);
|
|
125
|
+
await settle();
|
|
126
|
+
expect(scrollMock).toHaveBeenCalledWith(
|
|
127
|
+
wrapper.querySelector("#scroll-target-a"),
|
|
128
|
+
expect.any(Object),
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("hands a missing target-ref to the scroller as null", async () => {
|
|
133
|
+
fixture<HTMLScrollIntoViewElement>(
|
|
134
|
+
`<scroll-into-view target-ref="#does-not-exist"></scroll-into-view>`,
|
|
135
|
+
);
|
|
136
|
+
await settle();
|
|
137
|
+
expect(scrollMock).toHaveBeenCalledWith(null, expect.any(Object));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("scroll-into-view (triggers)", () => {
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
scrollMock.mockClear();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
afterEach(() => {
|
|
147
|
+
document.body.innerHTML = "";
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("does not scroll on connect when listen-for is set, only on the event", async () => {
|
|
151
|
+
const el = fixture<HTMLScrollIntoViewElement>(
|
|
152
|
+
`<scroll-into-view listen-for="click">Go</scroll-into-view>`,
|
|
153
|
+
);
|
|
154
|
+
await settle();
|
|
155
|
+
expect(scrollMock).not.toHaveBeenCalled();
|
|
156
|
+
el.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
157
|
+
await settle();
|
|
158
|
+
expect(scrollMock).toHaveBeenCalledTimes(1);
|
|
159
|
+
expect(scrollMock).toHaveBeenCalledWith(el, expect.any(Object));
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("does not scroll on connect when listen-for-lifecycle is set", async () => {
|
|
163
|
+
fixture<HTMLScrollIntoViewElement>(
|
|
164
|
+
`<scroll-into-view listen-for-lifecycle="disconnected"></scroll-into-view>`,
|
|
165
|
+
);
|
|
166
|
+
await settle();
|
|
167
|
+
expect(scrollMock).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
8
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
9
|
+
|
|
10
|
+
describe("simple view", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.innerHTML = "";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("mounts the auto-scroll target", async () => {
|
|
16
|
+
const { root } = await mountView(readDemo(import.meta.url, "simple"));
|
|
17
|
+
expect(root.querySelector("scroll-into-view")?.textContent).toMatch(
|
|
18
|
+
/Scrolled into view/,
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
8
|
+
import {
|
|
9
|
+
click,
|
|
10
|
+
flush,
|
|
11
|
+
mountView,
|
|
12
|
+
readDemo,
|
|
13
|
+
} from "@excom/quark/support/tests/view-helpers";
|
|
14
|
+
|
|
15
|
+
describe("sticky-header view", () => {
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
document.body.innerHTML = "";
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("jumps to section 3 on click", async () => {
|
|
21
|
+
const { root } = await mountView(readDemo(import.meta.url, "sticky-header"));
|
|
22
|
+
const target = root.querySelector("#section-3")!;
|
|
23
|
+
click(root.querySelector("[role='button']"));
|
|
24
|
+
await flush();
|
|
25
|
+
expect(target).toBeTruthy();
|
|
26
|
+
});
|
|
27
|
+
});
|