mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-17 04:19:40 +03:00
add server tests, improve readme and types
This commit is contained in:
+3
-1
@@ -31,8 +31,9 @@ const defaultRules = {
|
||||
groups: ['builtin', 'external', 'index', 'internal', 'unknown', 'type'],
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: '**/*.{css,scss,sass}',
|
||||
pattern: '*.{css,scss,sass}',
|
||||
group: 'unknown',
|
||||
patternOptions: { matchBase: true },
|
||||
position: 'after',
|
||||
},
|
||||
],
|
||||
@@ -46,6 +47,7 @@ const defaultRules = {
|
||||
},
|
||||
],
|
||||
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
|
||||
'react/require-default-props': ['off'],
|
||||
};
|
||||
const defaultExtends = ['airbnb', 'prettier', 'plugin:react/jsx-runtime'];
|
||||
const defaultPlugins = ['prettier', 'json', '@typescript-eslint', 'import', 'react'];
|
||||
|
||||
@@ -27,12 +27,13 @@
|
||||
|
||||
## Why
|
||||
|
||||
I've created this plugin because I hate ugly and space consuming scrollbars. Similar plugins haven't met my requirements in terms of features, quality, simplicity, license or browser support.
|
||||
I created this plugin because I hate ugly and space consuming scrollbars. Similar plugins haven't met my requirements in terms of features, quality, simplicity, license or browser support.
|
||||
|
||||
## Goals & Features
|
||||
|
||||
- Simple, powerful and good documented API
|
||||
- High browser compatibility - <b>Firefox</b>, <b>Chrome</b>, <b>Opera</b>, <b>Edge</b>, <b>Safari 10+</b> and <b>IE 11</b>
|
||||
- Can be run on the server - <b>SSR</b>, <b>SSG</b> and <b>ISR</b> support
|
||||
- Tested on various devices - <b>Mobile</b>, <b>Desktop</b> and <b>Tablet</b>
|
||||
- Tested with various (and mixed) inputs - <b>Mouse</b>, <b>touch</b> and <b>pen</b>
|
||||
- <b>Treeshaking</b> - bundle only what you really need
|
||||
@@ -85,6 +86,13 @@ You can initialize either directly with an `Element` or with an `Object` where y
|
||||
const osInstance = OverlayScrollbars(document.querySelector('#myElement'), {});
|
||||
```
|
||||
|
||||
### Bridging initialization flickering
|
||||
|
||||
If you initialize OverlayScrollbars it needs a few milliseconds to create and append all the elements to the DOM.
|
||||
While this period the native scrollbars are still visible and are switched out after the initialization is finished. This is perceived as flickering.
|
||||
|
||||
To fix this behavior apply the `data-overlayscrollbars=""` attribute to the target element (and `html` element if the target element is `body`).
|
||||
|
||||
<details><summary><h6>Initialization with an Object</h6></summary>
|
||||
|
||||
> __Note__: For now please refer to the <b>TypeScript definitions</b> for a more detailed description of all possibilities.
|
||||
@@ -444,6 +452,7 @@ You can write and publish your own Plugins. This section is a work in progress.
|
||||
|
||||
## Sponsors
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://www.browserstack.com" target="_blank">
|
||||
@@ -454,6 +463,7 @@ You can write and publish your own Plugins. This section is a work in progress.
|
||||
Thanks to <a href="https://www.browserstack.com" target="_blank">BrowserStack</a> for sponsoring open source projects and letting me test OverlayScrollbars for free.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Future Plans
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
@@ -4,9 +4,16 @@ const resolve = require('./resolve');
|
||||
// For a detailed explanation regarding each configuration property, visit:
|
||||
// https://jestjs.io/docs/en/configuration.html
|
||||
|
||||
const assetFilesModuleNameMapper = {
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||
path.resolve(__dirname, 'jest.fileMock.js'),
|
||||
'.*\\.(css|less|scss|sass)$': path.resolve(__dirname, 'jest.fileMock.js'),
|
||||
};
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
coverageDirectory: './.coverage/jest',
|
||||
moduleNameMapper: assetFilesModuleNameMapper,
|
||||
projects: [
|
||||
{
|
||||
displayName: 'node',
|
||||
@@ -15,9 +22,12 @@ module.exports = {
|
||||
clearMocks: true,
|
||||
moduleDirectories: resolve.directories,
|
||||
moduleFileExtensions: resolve.extensions.map((ext) => ext.replace(/\./, '')),
|
||||
moduleNameMapper: {
|
||||
...assetFilesModuleNameMapper,
|
||||
...resolve.paths.jest.moduleNameMapper,
|
||||
},
|
||||
testPathIgnorePatterns: ['\\\\node_modules\\\\'],
|
||||
setupFilesAfterEnv: [path.resolve(__dirname, './jest.setup.js')],
|
||||
...resolve.paths.jest,
|
||||
},
|
||||
{
|
||||
displayName: 'jsdom',
|
||||
@@ -26,6 +36,10 @@ module.exports = {
|
||||
clearMocks: true,
|
||||
moduleDirectories: resolve.directories,
|
||||
moduleFileExtensions: resolve.extensions.map((ext) => ext.replace(/\./, '')),
|
||||
moduleNameMapper: {
|
||||
...assetFilesModuleNameMapper,
|
||||
...resolve.paths.jest.moduleNameMapper,
|
||||
},
|
||||
testPathIgnorePatterns: ['\\\\node_modules\\\\'],
|
||||
setupFilesAfterEnv: [path.resolve(__dirname, './jest.setup.js')],
|
||||
...resolve.paths.jest,
|
||||
|
||||
@@ -16,32 +16,34 @@ export const esbuildPluginTailwind = ({
|
||||
build.onEnd(async (result) => {
|
||||
if (result) {
|
||||
const { metafile, outputFiles } = result;
|
||||
const { inputs } = metafile;
|
||||
const tailwindFile = outputFiles.find(({ path: outputFilePath }) =>
|
||||
tailwindCssFileRegex.test(outputFilePath)
|
||||
);
|
||||
|
||||
if (tailwindFile) {
|
||||
const { path: tailwindFilePath, text: tailwindFileCss } = tailwindFile;
|
||||
const tailwindContentGlobs = (resolvedTailwindConfig?.content || []).filter(
|
||||
(entry) => typeof entry === 'string'
|
||||
);
|
||||
const inputFilePaths = Object.keys(inputs).map((input) => path.resolve(input));
|
||||
const includedFiles = Array.from(
|
||||
new Set(
|
||||
tailwindContentGlobs
|
||||
.map((glob) => minimatch.match(inputFilePaths, glob, { dot: true }))
|
||||
.flat()
|
||||
)
|
||||
if (metafile && outputFiles) {
|
||||
const { inputs } = metafile;
|
||||
const tailwindFile = outputFiles.find(({ path: outputFilePath }) =>
|
||||
tailwindCssFileRegex.test(outputFilePath)
|
||||
);
|
||||
|
||||
const postcssResult = await postcss([
|
||||
tailwindcss({ ...(resolvedTailwindConfig || {}), content: includedFiles }),
|
||||
]).process(tailwindFileCss, {
|
||||
from: tailwindFilePath,
|
||||
});
|
||||
if (tailwindFile) {
|
||||
const { path: tailwindFilePath, text: tailwindFileCss } = tailwindFile;
|
||||
const tailwindContentGlobs = (resolvedTailwindConfig?.content || []).filter(
|
||||
(entry) => typeof entry === 'string'
|
||||
);
|
||||
const inputFilePaths = Object.keys(inputs).map((input) => path.resolve(input));
|
||||
const includedFiles = Array.from(
|
||||
new Set(
|
||||
tailwindContentGlobs
|
||||
.map((glob) => minimatch.match(inputFilePaths, glob, { dot: true }))
|
||||
.flat()
|
||||
)
|
||||
);
|
||||
|
||||
tailwindFile.contents = Buffer.from(postcssResult.css);
|
||||
const postcssResult = await postcss([
|
||||
tailwindcss({ ...(resolvedTailwindConfig || {}), content: includedFiles }),
|
||||
]).process(tailwindFileCss, {
|
||||
from: tailwindFilePath,
|
||||
});
|
||||
|
||||
tailwindFile.contents = Buffer.from(postcssResult.css);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Generated
+1514
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"main": "tailwind.config.js"
|
||||
"main": "tailwind.config.js",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,100 @@ const defaultTheme = require('tailwindcss/defaultTheme');
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
current: 'currentColor',
|
||||
'primary-cyan1': '#33FFFF',
|
||||
'primary-cyan2': '#87FED1',
|
||||
'primary-green': '#C0FEB1',
|
||||
'primary-blue1': '#338EFF',
|
||||
'primary-blue2': '#4276FF',
|
||||
'primary-violet': '#5D55FF',
|
||||
'primary-dark': '#0A376B',
|
||||
'primary-gray1': '#475774',
|
||||
'primary-gray2': '#697996',
|
||||
},
|
||||
transitionProperty: {
|
||||
transformColor: 'transform, color',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Noto Sans', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
typography: ({ theme }) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
b: {
|
||||
fontWeight: theme('fontWeight.medium'),
|
||||
},
|
||||
strong: {
|
||||
fontWeight: theme('fontWeight.medium'),
|
||||
},
|
||||
h5: {
|
||||
color: theme('colors.primary-dark'),
|
||||
fontWeight: theme('fontWeight.medium'),
|
||||
fontSize: theme('fontSize.sm'),
|
||||
},
|
||||
h6: {
|
||||
color: theme('colors.primary-dark'),
|
||||
fontWeight: theme('fontWeight.medium'),
|
||||
fontSize: theme('fontSize.sm'),
|
||||
},
|
||||
'blockquote > p > strong:first-child': {
|
||||
color: theme('colors.primary-blue2'),
|
||||
},
|
||||
'blockquote p:first-of-type::before': {
|
||||
content: '',
|
||||
},
|
||||
'blockquote p:last-of-type::after': {
|
||||
content: '',
|
||||
},
|
||||
code: {
|
||||
background: 'var(--tw-prose-pre-bg)',
|
||||
fontWeight: theme('fontWeight.medium'),
|
||||
padding: theme('padding[1]'),
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
},
|
||||
'code::before': {
|
||||
content: '',
|
||||
},
|
||||
'code::after': {
|
||||
content: '',
|
||||
},
|
||||
'summary > *:only-child,': {
|
||||
display: 'inline-block',
|
||||
},
|
||||
summary: {
|
||||
display: 'inline list-item',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
},
|
||||
primary: {
|
||||
css: {
|
||||
'--tw-prose-body': theme('colors.primary-gray1'),
|
||||
'--tw-prose-headings': theme('colors.primary-dark'),
|
||||
'--tw-prose-lead': theme('colors.primary-gray1'),
|
||||
'--tw-prose-links': theme('colors.primary-blue2'),
|
||||
'--tw-prose-bold': theme('colors.primary-dark'),
|
||||
'--tw-prose-counters': theme('colors.primary-gray1'),
|
||||
'--tw-prose-bullets': theme('colors.primary-blue2'),
|
||||
'--tw-prose-hr': theme('colors.slate[200]'),
|
||||
'--tw-prose-quotes': theme('colors.primary-dark'),
|
||||
'--tw-prose-quote-borders': theme('colors.slate[200]'),
|
||||
'--tw-prose-captions': theme('colors.primary-gray1'),
|
||||
'--tw-prose-code': theme('colors.primary-dark'),
|
||||
'--tw-prose-pre-code': theme('colors.pink[100]'),
|
||||
'--tw-prose-pre-bg': theme('colors.slate[100]'),
|
||||
'--tw-prose-th-borders': theme('colors.slate[200]'),
|
||||
'--tw-prose-td-borders': theme('colors.slate[200]'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
container: {
|
||||
center: true,
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Noto Sans', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
fontWeight: {
|
||||
normal: 400,
|
||||
medium: 600,
|
||||
@@ -25,5 +112,8 @@ module.exports = {
|
||||
xxl: '1536px',
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [
|
||||
// eslint-disable-next-line global-require
|
||||
require('@tailwindcss/typography'),
|
||||
],
|
||||
};
|
||||
|
||||
Generated
+3373
-243
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
||||
"@~local/rollup": "file:./local/rollup",
|
||||
"@~local/tailwind": "file:./local/tailwind",
|
||||
"@~local/tsconfig": "file:./local/tsconfig",
|
||||
"@~package/overlayscrollbars": "file:./packages/overlayscrollbars",
|
||||
"@babel/core": "^7.18.2",
|
||||
"@babel/plugin-transform-runtime": "^7.18.2",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
|
||||
@@ -27,12 +27,13 @@
|
||||
|
||||
## Why
|
||||
|
||||
I've created this plugin because I hate ugly and space consuming scrollbars. Similar plugins haven't met my requirements in terms of features, quality, simplicity, license or browser support.
|
||||
I created this plugin because I hate ugly and space consuming scrollbars. Similar plugins haven't met my requirements in terms of features, quality, simplicity, license or browser support.
|
||||
|
||||
## Goals & Features
|
||||
|
||||
- Simple, powerful and good documented API
|
||||
- High browser compatibility - <b>Firefox</b>, <b>Chrome</b>, <b>Opera</b>, <b>Edge</b>, <b>Safari 10+</b> and <b>IE 11</b>
|
||||
- Can be run on the server - <b>SSR</b>, <b>SSG</b> and <b>ISR</b> support
|
||||
- Tested on various devices - <b>Mobile</b>, <b>Desktop</b> and <b>Tablet</b>
|
||||
- Tested with various (and mixed) inputs - <b>Mouse</b>, <b>touch</b> and <b>pen</b>
|
||||
- <b>Treeshaking</b> - bundle only what you really need
|
||||
@@ -85,6 +86,13 @@ You can initialize either directly with an `Element` or with an `Object` where y
|
||||
const osInstance = OverlayScrollbars(document.querySelector('#myElement'), {});
|
||||
```
|
||||
|
||||
### Bridging initialization flickering
|
||||
|
||||
If you initialize OverlayScrollbars it needs a few milliseconds to create and append all the elements to the DOM.
|
||||
While this period the native scrollbars are still visible and are switched out after the initialization is finished. This is perceived as flickering.
|
||||
|
||||
To fix this behavior apply the `data-overlayscrollbars=""` attribute to the target element (and `html` element if the target element is `body`).
|
||||
|
||||
<details><summary><h6>Initialization with an Object</h6></summary>
|
||||
|
||||
> __Note__: For now please refer to the <b>TypeScript definitions</b> for a more detailed description of all possibilities.
|
||||
@@ -444,6 +452,7 @@ You can write and publish your own Plugins. This section is a work in progress.
|
||||
|
||||
## Sponsors
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://www.browserstack.com" target="_blank">
|
||||
@@ -454,6 +463,7 @@ You can write and publish your own Plugins. This section is a work in progress.
|
||||
Thanks to <a href="https://www.browserstack.com" target="_blank">BrowserStack</a> for sponsoring open source projects and letting me test OverlayScrollbars for free.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Future Plans
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import '~/index.scss';
|
||||
import './index.scss';
|
||||
|
||||
export { OverlayScrollbars } from '~/overlayscrollbars';
|
||||
export { ScrollbarsHidingPlugin, SizeObserverPlugin, ClickScrollPlugin } from '~/plugins';
|
||||
|
||||
@@ -78,11 +78,6 @@ const unwrap = (elm: HTMLElement | false | null | undefined) => {
|
||||
removeElements(elm);
|
||||
};
|
||||
|
||||
const addDataAttrHost = (elm: HTMLElement, value: string) => {
|
||||
attr(elm, dataAttributeHost, value);
|
||||
return removeAttr.bind(0, elm, dataAttributeHost);
|
||||
};
|
||||
|
||||
export const createStructureSetupElements = (
|
||||
target: InitializationTarget
|
||||
): StructureSetupElements => {
|
||||
@@ -113,14 +108,11 @@ export const createStructureSetupElements = (
|
||||
const targetElement = targetIsElm ? target : targetStructureInitialization.target;
|
||||
const isTextarea = is(targetElement, 'textarea');
|
||||
const ownerDocument = targetElement.ownerDocument;
|
||||
const docElement = ownerDocument.documentElement;
|
||||
const isBody = targetElement === ownerDocument.body;
|
||||
const wnd = ownerDocument.defaultView as Window;
|
||||
const staticInitializationElement = generalStaticInitializationElement<
|
||||
[InitializationTargetElement]
|
||||
>.bind(0, [targetElement]);
|
||||
const dynamicInitializationElement = generalDynamicInitializationElement<
|
||||
[InitializationTargetElement]
|
||||
>.bind(0, [targetElement]);
|
||||
const staticInitializationElement = generalStaticInitializationElement.bind(0, [targetElement]);
|
||||
const dynamicInitializationElement = generalDynamicInitializationElement.bind(0, [targetElement]);
|
||||
const viewportElement = staticInitializationElement(
|
||||
createNewDiv,
|
||||
defaultViewportInitialization,
|
||||
@@ -155,7 +147,7 @@ export const createStructureSetupElements = (
|
||||
!_nativeScrollbarsHiding &&
|
||||
createUniqueViewportArrangeElement &&
|
||||
createUniqueViewportArrangeElement(env),
|
||||
_scrollOffsetElement: viewportIsTargetBody ? ownerDocument.documentElement : viewportElement,
|
||||
_scrollOffsetElement: viewportIsTargetBody ? docElement : viewportElement,
|
||||
_scrollEventElement: viewportIsTargetBody ? ownerDocument : viewportElement,
|
||||
_windowElm: wnd,
|
||||
_documentElm: ownerDocument,
|
||||
@@ -179,7 +171,15 @@ export const createStructureSetupElements = (
|
||||
const elementIsGenerated = (elm: HTMLElement | false) =>
|
||||
elm ? indexOf(generatedElements, elm) > -1 : null;
|
||||
const { _target, _host, _padding, _viewport, _content, _viewportArrange } = evaluatedTargetObj;
|
||||
const destroyFns: (() => any)[] = [];
|
||||
const destroyFns: (() => any)[] = [
|
||||
() => {
|
||||
// always remove dataAttributeHost from host and from <html> element if target is body
|
||||
removeAttr(_host, dataAttributeHost);
|
||||
if (isBody) {
|
||||
removeAttr(docElement, dataAttributeHost);
|
||||
}
|
||||
},
|
||||
];
|
||||
const isTextareaHostGenerated = isTextarea && elementIsGenerated(_host);
|
||||
let targetContents = isTextarea
|
||||
? _target
|
||||
@@ -190,7 +190,8 @@ export const createStructureSetupElements = (
|
||||
);
|
||||
const contentSlot = _content || _viewport;
|
||||
const appendElements = () => {
|
||||
const removeHostDataAttr = addDataAttrHost(_host, viewportIsTarget ? 'viewport' : 'host');
|
||||
attr(_host, dataAttributeHost, viewportIsTarget ? 'viewport' : 'host');
|
||||
|
||||
const removePaddingClass = addClass(_padding, classNamePadding);
|
||||
const removeViewportClass = addClass(_viewport, !viewportIsTarget && classNameViewport);
|
||||
const removeContentClass = addClass(_content, classNameContent);
|
||||
@@ -215,7 +216,6 @@ export const createStructureSetupElements = (
|
||||
|
||||
push(destroyFns, () => {
|
||||
removeHtmlClass();
|
||||
removeHostDataAttr();
|
||||
removeAttr(_viewport, dataAttributeHostOverflowX);
|
||||
removeAttr(_viewport, dataAttributeHostOverflowY);
|
||||
|
||||
|
||||
+7
-9
@@ -416,18 +416,16 @@ export const createOverflowUpdateSegment: CreateStructureUpdateSegment = (
|
||||
};
|
||||
const overflowAmountClientSize = {
|
||||
w: max0(
|
||||
viewportIsTargetBody
|
||||
(viewportIsTargetBody
|
||||
? _windowElm.innerWidth
|
||||
: arrangedViewportClientSize.w +
|
||||
max0(viewportclientSize.w - viewportScrollSize.w) +
|
||||
sizeFraction.w
|
||||
: arrangedViewportClientSize.w + max0(viewportclientSize.w - viewportScrollSize.w)) +
|
||||
sizeFraction.w
|
||||
),
|
||||
h: max0(
|
||||
viewportIsTargetBody
|
||||
? _windowElm.innerHeight
|
||||
: arrangedViewportClientSize.h +
|
||||
max0(viewportclientSize.h - viewportScrollSize.h) +
|
||||
sizeFraction.h
|
||||
(viewportIsTargetBody
|
||||
? _windowElm.innerHeight + sizeFraction.h
|
||||
: arrangedViewportClientSize.h + max0(viewportclientSize.h - viewportScrollSize.h)) +
|
||||
sizeFraction.h
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isClient } from '~/support/compatibility/server';
|
||||
import { jsAPI } from '~/support/compatibility/vendors';
|
||||
|
||||
export const MutationObserverConstructor = jsAPI<typeof MutationObserver>('MutationObserver');
|
||||
@@ -6,5 +7,8 @@ export const IntersectionObserverConstructor =
|
||||
export const ResizeObserverConstructor = jsAPI<typeof ResizeObserver>('ResizeObserver');
|
||||
export const cAF = jsAPI<typeof cancelAnimationFrame>('cancelAnimationFrame');
|
||||
export const rAF = jsAPI<typeof requestAnimationFrame>('requestAnimationFrame');
|
||||
export const setT = window.setTimeout as (handler: TimerHandler, timeout?: number) => number;
|
||||
export const clearT = window.clearTimeout as (id?: number) => void;
|
||||
export const setT = (isClient() && window.setTimeout) as (
|
||||
handler: TimerHandler,
|
||||
timeout?: number
|
||||
) => number;
|
||||
export const clearT = (isClient() && window.clearTimeout) as (id?: number) => void;
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from '~/support/compatibility/vendors';
|
||||
export * from '~/support/compatibility/apis';
|
||||
export * from '~/support/compatibility/server';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const isClient = () => typeof window !== 'undefined';
|
||||
@@ -1,4 +1,5 @@
|
||||
import { each } from '~/support/utils/array';
|
||||
import { isClient } from '~/support/compatibility/server';
|
||||
import { hasOwnProperty } from '~/support/utils/object';
|
||||
import { createDiv } from '~/support/dom/create';
|
||||
|
||||
@@ -95,17 +96,19 @@ export const cssPropertyValue = (property: string, values: string, suffix?: stri
|
||||
* @param name The name of the JS function, object or constructor.
|
||||
*/
|
||||
export const jsAPI = <T = any>(name: string): T | undefined => {
|
||||
let result: any = jsCache[name] || window[name];
|
||||
if (isClient()) {
|
||||
let result: any = jsCache[name] || window[name];
|
||||
|
||||
if (hasOwnProperty(jsCache, name)) {
|
||||
if (hasOwnProperty(jsCache, name)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
each(jsPrefixes, (prefix: string) => {
|
||||
result = result || window[prefix + firstLetterToUpper(name)];
|
||||
return !result;
|
||||
});
|
||||
|
||||
jsCache[name] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
each(jsPrefixes, (prefix: string) => {
|
||||
result = result || window[prefix + firstLetterToUpper(name)];
|
||||
return !result;
|
||||
});
|
||||
|
||||
jsCache[name] = result;
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { isClient } from '~/support/compatibility/server';
|
||||
import { isElement } from '~/support/utils/types';
|
||||
import { push, from } from '~/support/utils/array';
|
||||
|
||||
type InputElementType = Node | Element | Node | false | null | undefined;
|
||||
type OutputElementType = Node | Element | null;
|
||||
|
||||
const elmPrototype = Element.prototype;
|
||||
const getElmPrototype = (isClient() && Element.prototype) as Element; // only Element.prototype wont work on server
|
||||
|
||||
/**
|
||||
* Find all elements with the passed selector, outgoing (and including) the passed element or the document if no element was provided.
|
||||
@@ -38,8 +39,9 @@ const is = (elm: InputElementType, selector: string): boolean => {
|
||||
if (isElement(elm)) {
|
||||
/* istanbul ignore next */
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const fn: (...args: any) => boolean = elmPrototype.matches || elmPrototype.msMatchesSelector;
|
||||
const fn: (...args: any) => boolean =
|
||||
// @ts-ignore
|
||||
getElmPrototype.matches || getElmPrototype.msMatchesSelector;
|
||||
return fn.call(elm, selector);
|
||||
}
|
||||
return false;
|
||||
@@ -76,7 +78,7 @@ const parent = (elm: InputElementType): OutputElementType => (elm ? elm.parentEl
|
||||
|
||||
const closest = (elm: InputElementType, selector: string): OutputElementType => {
|
||||
if (isElement(elm)) {
|
||||
const closestFn = elmPrototype.closest;
|
||||
const closestFn = getElmPrototype.closest;
|
||||
if (closestFn) {
|
||||
return closestFn.call(elm, selector);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { isClient } from '~/support/compatibility/server';
|
||||
import type { PlainObject } from '~/typings';
|
||||
|
||||
const ElementNodeType = Node.ELEMENT_NODE;
|
||||
const ElementNodeType = isClient() && Node.ELEMENT_NODE;
|
||||
const { toString, hasOwnProperty } = Object.prototype;
|
||||
|
||||
export const isUndefined = (obj: any): obj is undefined => obj === undefined;
|
||||
|
||||
+40
-3
@@ -130,14 +130,20 @@ const assertCorrectDOMStructure = (targetType: TargetType, viewportIsTarget: boo
|
||||
|
||||
const createStructureSetupElementsProxy = (
|
||||
target: InitializationTarget,
|
||||
tabindex?: boolean
|
||||
options: { tabindex?: boolean; autoAppend?: boolean } = {
|
||||
tabindex: false,
|
||||
autoAppend: true,
|
||||
}
|
||||
): StructureSetupElementsProxy => {
|
||||
const { tabindex, autoAppend } = options;
|
||||
const [elements, appendElements, destroy] = createStructureSetupElements(target);
|
||||
// simulate tabindex inheritance from host via mutation observer
|
||||
if (tabindex) {
|
||||
elements._viewport.setAttribute('tabindex', elements._target.getAttribute('tabindex')!);
|
||||
}
|
||||
appendElements();
|
||||
if (autoAppend) {
|
||||
appendElements();
|
||||
}
|
||||
return {
|
||||
input: target,
|
||||
elements,
|
||||
@@ -1121,7 +1127,7 @@ describe('structureSetup.elements', () => {
|
||||
const target = document.body.firstElementChild as HTMLElement;
|
||||
target.focus();
|
||||
|
||||
const { elements } = createStructureSetupElementsProxy(target, true);
|
||||
const { elements } = createStructureSetupElementsProxy(target, { tabindex: true });
|
||||
expect(elements._viewport.getAttribute('tabindex')).toBe('-1');
|
||||
expect(document.activeElement).toBe(elements._viewport);
|
||||
|
||||
@@ -1149,4 +1155,35 @@ describe('structureSetup.elements', () => {
|
||||
expect(preInitFocus).toBe(document.activeElement);
|
||||
});
|
||||
});
|
||||
|
||||
describe('data-overlayscrollbars attribute', () => {
|
||||
test('already set data-overlayscrollbars attribute is removed', () => {
|
||||
const target = document.body;
|
||||
target.setAttribute(dataAttributeHost, '');
|
||||
|
||||
const { destroy } = createStructureSetupElementsProxy(target);
|
||||
destroy();
|
||||
|
||||
expect(document.body.getAttribute(dataAttributeHost)).toBe(null);
|
||||
});
|
||||
|
||||
test('already set data-overlayscrollbars attribute is removed even if initialization gets canceled', () => {
|
||||
const target = document.body;
|
||||
target.setAttribute(dataAttributeHost, '');
|
||||
|
||||
const { destroy } = createStructureSetupElementsProxy(target, { autoAppend: false });
|
||||
destroy();
|
||||
|
||||
expect(document.body.getAttribute(dataAttributeHost)).toBe(null);
|
||||
});
|
||||
|
||||
test('already set data-overlayscrollbars attribute on html element is removed if target is body', () => {
|
||||
document.documentElement.setAttribute(dataAttributeHost, '');
|
||||
|
||||
const { destroy } = createStructureSetupElementsProxy(document.body);
|
||||
destroy();
|
||||
|
||||
expect(document.documentElement.getAttribute(dataAttributeHost)).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
describe('usage on server', () => {
|
||||
test('import', async () => {
|
||||
await expect(
|
||||
(async () => {
|
||||
const module = await import('~/index');
|
||||
return module;
|
||||
})()
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
test('static functions', async () => {
|
||||
await expect(
|
||||
(async () => {
|
||||
const { OverlayScrollbars } = await import('~/index');
|
||||
|
||||
expect(() => {
|
||||
OverlayScrollbars.valid(false);
|
||||
}).not.toThrow();
|
||||
expect(() => {
|
||||
OverlayScrollbars.plugin({});
|
||||
}).not.toThrow();
|
||||
})()
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user