@operato/headroom 8.0.0-beta.0 → 8.0.0-beta.1
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/CHANGELOG.md +9 -0
- package/package.json +2 -2
- package/.editorconfig +0 -29
- package/src/features.ts +0 -22
- package/src/headroom.ts +0 -251
- package/src/index.ts +0 -3
- package/src/scroller.ts +0 -82
- package/src/trackScroll.ts +0 -54
- package/tsconfig.json +0 -24
- package/web-dev-server.config.mjs +0 -27
- package/web-test-runner.config.mjs +0 -41
package/CHANGELOG.md
CHANGED
@@ -3,6 +3,15 @@
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
5
5
|
|
6
|
+
## [8.0.0-beta.1](https://github.com/hatiolab/operato/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2025-01-08)
|
7
|
+
|
8
|
+
|
9
|
+
### :bug: Bug Fix
|
10
|
+
|
11
|
+
* missing .npmignore ([be05985](https://github.com/hatiolab/operato/commit/be05985abfae4af53501f718dd52932099f7fbcb))
|
12
|
+
|
13
|
+
|
14
|
+
|
6
15
|
## [8.0.0-beta.0](https://github.com/hatiolab/operato/compare/v8.0.0-alpha.56...v8.0.0-beta.0) (2025-01-07)
|
7
16
|
|
8
17
|
**Note:** Version bump only for package @operato/headroom
|
package/package.json
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
"name": "@operato/headroom",
|
3
3
|
"description": "Webcomponent headroom following open-wc recommendations",
|
4
4
|
"author": "heartyoh",
|
5
|
-
"version": "8.0.0-beta.
|
5
|
+
"version": "8.0.0-beta.1",
|
6
6
|
"main": "dist/src/index.js",
|
7
7
|
"module": "dist/src/index.js",
|
8
8
|
"license": "MIT",
|
@@ -62,5 +62,5 @@
|
|
62
62
|
"prettier --write"
|
63
63
|
]
|
64
64
|
},
|
65
|
-
"gitHead": "
|
65
|
+
"gitHead": "d5b28a2e9deb632c0dc80132f6a7196dd6fe4220"
|
66
66
|
}
|
package/.editorconfig
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# EditorConfig helps developers define and maintain consistent
|
2
|
-
# coding styles between different editors and IDEs
|
3
|
-
# editorconfig.org
|
4
|
-
|
5
|
-
root = true
|
6
|
-
|
7
|
-
|
8
|
-
[*]
|
9
|
-
|
10
|
-
# Change these settings to your own preference
|
11
|
-
indent_style = space
|
12
|
-
indent_size = 2
|
13
|
-
|
14
|
-
# We recommend you to keep these unchanged
|
15
|
-
end_of_line = lf
|
16
|
-
charset = utf-8
|
17
|
-
trim_trailing_whitespace = true
|
18
|
-
insert_final_newline = true
|
19
|
-
|
20
|
-
[*.md]
|
21
|
-
trim_trailing_whitespace = false
|
22
|
-
|
23
|
-
[*.json]
|
24
|
-
indent_size = 2
|
25
|
-
|
26
|
-
[*.{html,js,md}]
|
27
|
-
block_comment_start = /**
|
28
|
-
block_comment = *
|
29
|
-
block_comment_end = */
|
package/src/features.ts
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Used to detect browser support for adding an event listener with options
|
3
|
-
* Credit: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
|
4
|
-
*/
|
5
|
-
export function passiveEventsSupported() {
|
6
|
-
var supported = false
|
7
|
-
|
8
|
-
try {
|
9
|
-
var options = {
|
10
|
-
get passive(): boolean {
|
11
|
-
supported = true
|
12
|
-
return true
|
13
|
-
}
|
14
|
-
}
|
15
|
-
window.addEventListener('test', options as any, options)
|
16
|
-
window.removeEventListener('test', options as any, options as any)
|
17
|
-
} catch (err) {
|
18
|
-
supported = false
|
19
|
-
}
|
20
|
-
|
21
|
-
return supported
|
22
|
-
}
|
package/src/headroom.ts
DELETED
@@ -1,251 +0,0 @@
|
|
1
|
-
import trackScroll from './trackScroll'
|
2
|
-
|
3
|
-
function normalizeUpDown(t: any) {
|
4
|
-
return t === Object(t) ? t : { down: t, up: t }
|
5
|
-
}
|
6
|
-
|
7
|
-
/**
|
8
|
-
* UI enhancement for fixed headers.
|
9
|
-
* Hides header when scrolling down
|
10
|
-
* Shows header when scrolling up
|
11
|
-
* @constructor
|
12
|
-
* @param {DOMElement} elem the header element
|
13
|
-
* @param {Object} options options for the widget
|
14
|
-
*/
|
15
|
-
export default class Headroom {
|
16
|
-
/**
|
17
|
-
* Default options
|
18
|
-
* @type {Object}
|
19
|
-
*/
|
20
|
-
static options = {
|
21
|
-
tolerance: {
|
22
|
-
up: 0,
|
23
|
-
down: 0
|
24
|
-
},
|
25
|
-
offset: 0,
|
26
|
-
scroller: window,
|
27
|
-
classes: {
|
28
|
-
frozen: 'headroom--frozen',
|
29
|
-
pinned: 'headroom--pinned',
|
30
|
-
unpinned: 'headroom--unpinned',
|
31
|
-
top: 'headroom--top',
|
32
|
-
notTop: 'headroom--not-top',
|
33
|
-
bottom: 'headroom--bottom',
|
34
|
-
notBottom: 'headroom--not-bottom',
|
35
|
-
initial: 'headroom'
|
36
|
-
}
|
37
|
-
}
|
38
|
-
|
39
|
-
private classes: { [key: string]: any }
|
40
|
-
private elem: HTMLElement
|
41
|
-
private tolerance: any
|
42
|
-
private offset: any
|
43
|
-
private initialised: boolean
|
44
|
-
private frozen: boolean
|
45
|
-
private scrollTracker: any
|
46
|
-
private scroller: any
|
47
|
-
|
48
|
-
private onPin?: Function
|
49
|
-
private onUnpin?: Function
|
50
|
-
private onTop?: Function
|
51
|
-
private onNotTop?: Function
|
52
|
-
private onBottom?: Function
|
53
|
-
private onNotBottom?: Function
|
54
|
-
|
55
|
-
constructor(elem: HTMLElement, options: any) {
|
56
|
-
options = options || {}
|
57
|
-
Object.assign(this, Headroom.options, options)
|
58
|
-
this.classes = Object.assign({}, Headroom.options.classes, options.classes)
|
59
|
-
|
60
|
-
this.elem = elem
|
61
|
-
this.tolerance = normalizeUpDown(this.tolerance)
|
62
|
-
this.offset = normalizeUpDown(this.offset)
|
63
|
-
this.initialised = false
|
64
|
-
this.frozen = false
|
65
|
-
}
|
66
|
-
|
67
|
-
/**
|
68
|
-
* Start listening to scrolling
|
69
|
-
* @public
|
70
|
-
*/
|
71
|
-
init() {
|
72
|
-
if (!this.initialised) {
|
73
|
-
this.addClass('initial')
|
74
|
-
this.initialised = true
|
75
|
-
|
76
|
-
// defer event registration to handle browser
|
77
|
-
// potentially restoring previous scroll position
|
78
|
-
setTimeout(
|
79
|
-
function (self) {
|
80
|
-
self.scrollTracker = trackScroll(
|
81
|
-
self.scroller,
|
82
|
-
{ offset: self.offset, tolerance: self.tolerance },
|
83
|
-
self.update.bind(self)
|
84
|
-
)
|
85
|
-
},
|
86
|
-
100,
|
87
|
-
this
|
88
|
-
)
|
89
|
-
}
|
90
|
-
|
91
|
-
return this
|
92
|
-
}
|
93
|
-
|
94
|
-
/**
|
95
|
-
* Destroy the widget, clearing up after itself
|
96
|
-
* @public
|
97
|
-
*/
|
98
|
-
destroy() {
|
99
|
-
this.initialised = false
|
100
|
-
Object.keys(this.classes).forEach(this.removeClass, this)
|
101
|
-
this.scrollTracker?.destroy()
|
102
|
-
}
|
103
|
-
|
104
|
-
/**
|
105
|
-
* Unpin the element
|
106
|
-
* @public
|
107
|
-
*/
|
108
|
-
unpin() {
|
109
|
-
if (this.hasClass('pinned') || !this.hasClass('unpinned')) {
|
110
|
-
this.addClass('unpinned')
|
111
|
-
this.removeClass('pinned')
|
112
|
-
|
113
|
-
if (this.onUnpin) {
|
114
|
-
this.onUnpin.call(this)
|
115
|
-
}
|
116
|
-
}
|
117
|
-
}
|
118
|
-
|
119
|
-
/**
|
120
|
-
* Pin the element
|
121
|
-
* @public
|
122
|
-
*/
|
123
|
-
pin() {
|
124
|
-
if (this.hasClass('unpinned')) {
|
125
|
-
this.addClass('pinned')
|
126
|
-
this.removeClass('unpinned')
|
127
|
-
|
128
|
-
if (this.onPin) {
|
129
|
-
this.onPin.call(this)
|
130
|
-
}
|
131
|
-
}
|
132
|
-
}
|
133
|
-
|
134
|
-
/**
|
135
|
-
* Freezes the current state of the widget
|
136
|
-
* @public
|
137
|
-
*/
|
138
|
-
freeze() {
|
139
|
-
this.frozen = true
|
140
|
-
this.addClass('frozen')
|
141
|
-
}
|
142
|
-
|
143
|
-
/**
|
144
|
-
* Re-enables the default behaviour of the widget
|
145
|
-
* @public
|
146
|
-
*/
|
147
|
-
unfreeze() {
|
148
|
-
this.frozen = false
|
149
|
-
this.removeClass('frozen')
|
150
|
-
}
|
151
|
-
|
152
|
-
top() {
|
153
|
-
if (!this.hasClass('top')) {
|
154
|
-
this.addClass('top')
|
155
|
-
this.removeClass('notTop')
|
156
|
-
|
157
|
-
if (this.onTop) {
|
158
|
-
this.onTop.call(this)
|
159
|
-
}
|
160
|
-
}
|
161
|
-
}
|
162
|
-
|
163
|
-
notTop() {
|
164
|
-
if (!this.hasClass('notTop')) {
|
165
|
-
this.addClass('notTop')
|
166
|
-
this.removeClass('top')
|
167
|
-
|
168
|
-
if (this.onNotTop) {
|
169
|
-
this.onNotTop.call(this)
|
170
|
-
}
|
171
|
-
}
|
172
|
-
}
|
173
|
-
|
174
|
-
bottom() {
|
175
|
-
if (!this.hasClass('bottom')) {
|
176
|
-
this.addClass('bottom')
|
177
|
-
this.removeClass('notBottom')
|
178
|
-
|
179
|
-
if (this.onBottom) {
|
180
|
-
this.onBottom.call(this)
|
181
|
-
}
|
182
|
-
}
|
183
|
-
}
|
184
|
-
|
185
|
-
notBottom() {
|
186
|
-
if (!this.hasClass('notBottom')) {
|
187
|
-
this.addClass('notBottom')
|
188
|
-
this.removeClass('bottom')
|
189
|
-
|
190
|
-
if (this.onNotBottom) {
|
191
|
-
this.onNotBottom.call(this)
|
192
|
-
}
|
193
|
-
}
|
194
|
-
}
|
195
|
-
|
196
|
-
shouldUnpin(details: any) {
|
197
|
-
var scrollingDown = details.direction === 'down'
|
198
|
-
|
199
|
-
return scrollingDown && !details.top && details.toleranceExceeded
|
200
|
-
}
|
201
|
-
|
202
|
-
shouldPin(details: any) {
|
203
|
-
var scrollingUp = details.direction === 'up'
|
204
|
-
|
205
|
-
return (scrollingUp && details.toleranceExceeded) || details.top
|
206
|
-
}
|
207
|
-
|
208
|
-
addClass(className: string) {
|
209
|
-
this.elem.classList.add.apply(this.elem.classList, this.classes[className].split(' '))
|
210
|
-
}
|
211
|
-
|
212
|
-
removeClass(className: string) {
|
213
|
-
this.elem.classList.remove.apply(this.elem.classList, this.classes[className].split(' '))
|
214
|
-
}
|
215
|
-
|
216
|
-
hasClass(className: string) {
|
217
|
-
return this.classes[className].split(' ').every(function (cls: string) {
|
218
|
-
//@ts-ignore
|
219
|
-
return this.classList.contains(cls)
|
220
|
-
}, this.elem)
|
221
|
-
}
|
222
|
-
|
223
|
-
update(details: any) {
|
224
|
-
if (details.isOutOfBounds) {
|
225
|
-
// Ignore bouncy scrolling in OSX
|
226
|
-
return
|
227
|
-
}
|
228
|
-
|
229
|
-
if (this.frozen === true) {
|
230
|
-
return
|
231
|
-
}
|
232
|
-
|
233
|
-
if (details.top) {
|
234
|
-
this.top()
|
235
|
-
} else {
|
236
|
-
this.notTop()
|
237
|
-
}
|
238
|
-
|
239
|
-
if (details.bottom) {
|
240
|
-
this.bottom()
|
241
|
-
} else {
|
242
|
-
this.notBottom()
|
243
|
-
}
|
244
|
-
|
245
|
-
if (this.shouldUnpin(details)) {
|
246
|
-
this.unpin()
|
247
|
-
} else if (this.shouldPin(details)) {
|
248
|
-
this.pin()
|
249
|
-
}
|
250
|
-
}
|
251
|
-
}
|
package/src/index.ts
DELETED
package/src/scroller.ts
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
function isDocument(obj: any) {
|
2
|
-
return obj.nodeType === 9 // Node.DOCUMENT_NODE === 9
|
3
|
-
}
|
4
|
-
|
5
|
-
function isWindow(obj: any) {
|
6
|
-
// `obj === window` or `obj instanceof Window` is not sufficient,
|
7
|
-
// as the obj may be the window of an iframe.
|
8
|
-
return obj && obj.document && isDocument(obj.document)
|
9
|
-
}
|
10
|
-
|
11
|
-
function windowScroller(win: any) {
|
12
|
-
var doc = win.document
|
13
|
-
var body = doc.body
|
14
|
-
var html = doc.documentElement
|
15
|
-
|
16
|
-
return {
|
17
|
-
/**
|
18
|
-
* @see http://james.padolsey.com/javascript/get-document-height-cross-browser/
|
19
|
-
* @return {Number} the scroll height of the document in pixels
|
20
|
-
*/
|
21
|
-
scrollHeight: function () {
|
22
|
-
return Math.max(
|
23
|
-
body.scrollHeight,
|
24
|
-
html.scrollHeight,
|
25
|
-
body.offsetHeight,
|
26
|
-
html.offsetHeight,
|
27
|
-
body.clientHeight,
|
28
|
-
html.clientHeight
|
29
|
-
)
|
30
|
-
},
|
31
|
-
|
32
|
-
/**
|
33
|
-
* @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript
|
34
|
-
* @return {Number} the height of the viewport in pixels
|
35
|
-
*/
|
36
|
-
height: function () {
|
37
|
-
return win.innerHeight || html.clientHeight || body.clientHeight
|
38
|
-
},
|
39
|
-
|
40
|
-
/**
|
41
|
-
* Gets the Y scroll position
|
42
|
-
* @return {Number} pixels the page has scrolled along the Y-axis
|
43
|
-
*/
|
44
|
-
scrollY: function () {
|
45
|
-
if (win.pageYOffset !== undefined) {
|
46
|
-
return win.pageYOffset
|
47
|
-
}
|
48
|
-
|
49
|
-
return (html || body.parentNode || body).scrollTop
|
50
|
-
}
|
51
|
-
}
|
52
|
-
}
|
53
|
-
|
54
|
-
function elementScroller(element: HTMLElement) {
|
55
|
-
return {
|
56
|
-
/**
|
57
|
-
* @return {Number} the scroll height of the element in pixels
|
58
|
-
*/
|
59
|
-
scrollHeight: function () {
|
60
|
-
return Math.max(element.scrollHeight, element.offsetHeight, element.clientHeight)
|
61
|
-
},
|
62
|
-
|
63
|
-
/**
|
64
|
-
* @return {Number} the height of the element in pixels
|
65
|
-
*/
|
66
|
-
height: function () {
|
67
|
-
return Math.max(element.offsetHeight, element.clientHeight)
|
68
|
-
},
|
69
|
-
|
70
|
-
/**
|
71
|
-
* Gets the Y scroll position
|
72
|
-
* @return {Number} pixels the element has scrolled along the Y-axis
|
73
|
-
*/
|
74
|
-
scrollY: function () {
|
75
|
-
return element.scrollTop
|
76
|
-
}
|
77
|
-
}
|
78
|
-
}
|
79
|
-
|
80
|
-
export default function createScroller(element: HTMLElement) {
|
81
|
-
return isWindow(element) ? windowScroller(element) : elementScroller(element)
|
82
|
-
}
|
package/src/trackScroll.ts
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
import createScroller from './scroller'
|
2
|
-
import { passiveEventsSupported } from './features'
|
3
|
-
|
4
|
-
/**
|
5
|
-
* @param element EventTarget
|
6
|
-
*/
|
7
|
-
export default function trackScroll(element: EventTarget, options: any, callback: Function) {
|
8
|
-
var isPassiveSupported = passiveEventsSupported()
|
9
|
-
var rafId: number
|
10
|
-
var scrolled = false
|
11
|
-
var scroller = createScroller(element as HTMLElement)
|
12
|
-
var lastScrollY = scroller.scrollY()
|
13
|
-
var details = {} as any
|
14
|
-
|
15
|
-
function update() {
|
16
|
-
var scrollY = Math.round(scroller.scrollY())
|
17
|
-
var height = scroller.height()
|
18
|
-
var scrollHeight = scroller.scrollHeight()
|
19
|
-
|
20
|
-
// reuse object for less memory churn
|
21
|
-
details.scrollY = scrollY
|
22
|
-
details.lastScrollY = lastScrollY
|
23
|
-
details.direction = scrollY > lastScrollY ? 'down' : 'up'
|
24
|
-
details.distance = Math.abs(scrollY - lastScrollY)
|
25
|
-
details.isOutOfBounds = scrollY < 0 || scrollY + height > scrollHeight
|
26
|
-
details.top = scrollY <= options.offset[details.direction]
|
27
|
-
details.bottom = scrollY + height >= scrollHeight
|
28
|
-
details.toleranceExceeded = details.distance > options.tolerance[details.direction]
|
29
|
-
|
30
|
-
callback(details)
|
31
|
-
|
32
|
-
lastScrollY = scrollY
|
33
|
-
scrolled = false
|
34
|
-
}
|
35
|
-
|
36
|
-
function handleScroll() {
|
37
|
-
if (!scrolled) {
|
38
|
-
scrolled = true
|
39
|
-
rafId = requestAnimationFrame(update)
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
var eventOptions = isPassiveSupported ? { passive: true, capture: false } : false
|
44
|
-
|
45
|
-
element.addEventListener('scroll', handleScroll, eventOptions)
|
46
|
-
update()
|
47
|
-
|
48
|
-
return {
|
49
|
-
destroy: function () {
|
50
|
-
cancelAnimationFrame(rafId)
|
51
|
-
element.removeEventListener('scroll', handleScroll, eventOptions)
|
52
|
-
}
|
53
|
-
}
|
54
|
-
}
|
package/tsconfig.json
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"compilerOptions": {
|
3
|
-
"target": "es2018",
|
4
|
-
"module": "esnext",
|
5
|
-
"moduleResolution": "node",
|
6
|
-
"noEmitOnError": true,
|
7
|
-
"lib": ["es2017", "dom"],
|
8
|
-
"strict": true,
|
9
|
-
"esModuleInterop": false,
|
10
|
-
"allowSyntheticDefaultImports": true,
|
11
|
-
"experimentalDecorators": true,
|
12
|
-
"useDefineForClassFields": false,
|
13
|
-
"importHelpers": true,
|
14
|
-
"outDir": "dist",
|
15
|
-
"sourceMap": true,
|
16
|
-
"inlineSources": true,
|
17
|
-
"rootDir": "./",
|
18
|
-
"declaration": true,
|
19
|
-
"incremental": true,
|
20
|
-
"skipLibCheck": true,
|
21
|
-
"types": ["node", "mocha"]
|
22
|
-
},
|
23
|
-
"include": ["**/*.ts", "*.d.ts"]
|
24
|
-
}
|
@@ -1,27 +0,0 @@
|
|
1
|
-
// import { hmrPlugin, presets } from '@open-wc/dev-server-hmr';
|
2
|
-
|
3
|
-
/** Use Hot Module replacement by adding --hmr to the start command */
|
4
|
-
const hmr = process.argv.includes('--hmr');
|
5
|
-
|
6
|
-
export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
|
7
|
-
open: '/demo/',
|
8
|
-
/** Use regular watch mode if HMR is not enabled. */
|
9
|
-
watch: !hmr,
|
10
|
-
/** Resolve bare module imports */
|
11
|
-
nodeResolve: {
|
12
|
-
exportConditions: ['browser', 'development'],
|
13
|
-
},
|
14
|
-
|
15
|
-
/** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */
|
16
|
-
// esbuildTarget: 'auto'
|
17
|
-
|
18
|
-
/** Set appIndex to enable SPA routing */
|
19
|
-
// appIndex: 'demo/index.html',
|
20
|
-
|
21
|
-
plugins: [
|
22
|
-
/** Use Hot Module Replacement by uncommenting. Requires @open-wc/dev-server-hmr plugin */
|
23
|
-
// hmr && hmrPlugin({ exclude: ['**/*/node_modules/**/*'], presets: [presets.litElement] }),
|
24
|
-
],
|
25
|
-
|
26
|
-
// See documentation for all available options
|
27
|
-
});
|
@@ -1,41 +0,0 @@
|
|
1
|
-
// import { playwrightLauncher } from '@web/test-runner-playwright';
|
2
|
-
|
3
|
-
const filteredLogs = ['Running in dev mode', 'lit-html is in dev mode'];
|
4
|
-
|
5
|
-
export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({
|
6
|
-
/** Test files to run */
|
7
|
-
files: 'dist/test/**/*.test.js',
|
8
|
-
|
9
|
-
/** Resolve bare module imports */
|
10
|
-
nodeResolve: {
|
11
|
-
exportConditions: ['browser', 'development'],
|
12
|
-
},
|
13
|
-
|
14
|
-
/** Filter out lit dev mode logs */
|
15
|
-
filterBrowserLogs(log) {
|
16
|
-
for (const arg of log.args) {
|
17
|
-
if (typeof arg === 'string' && filteredLogs.some(l => arg.includes(l))) {
|
18
|
-
return false;
|
19
|
-
}
|
20
|
-
}
|
21
|
-
return true;
|
22
|
-
},
|
23
|
-
|
24
|
-
/** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */
|
25
|
-
// esbuildTarget: 'auto',
|
26
|
-
|
27
|
-
/** Amount of browsers to run concurrently */
|
28
|
-
// concurrentBrowsers: 2,
|
29
|
-
|
30
|
-
/** Amount of test files per browser to test concurrently */
|
31
|
-
// concurrency: 1,
|
32
|
-
|
33
|
-
/** Browsers to run tests on */
|
34
|
-
// browsers: [
|
35
|
-
// playwrightLauncher({ product: 'chromium' }),
|
36
|
-
// playwrightLauncher({ product: 'firefox' }),
|
37
|
-
// playwrightLauncher({ product: 'webkit' }),
|
38
|
-
// ],
|
39
|
-
|
40
|
-
// See documentation for all available options
|
41
|
-
});
|