-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: create affix component * chore: complete basic function * test: added affix component unit test * docs: added affix component document --------- Co-authored-by: baiwusanyu-c <740132583@qq.com>
- Loading branch information
1 parent
e7f5c61
commit 36469b3
Showing
18 changed files
with
550 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
||
exports[`Test: KAffix > props: cls 1`] = `"<head></head><body style=\\"height: 100px; overflow: auto;\\"><div class=\\"k-affix k-affix--test\\"></div></body>"`; | ||
exports[`Test: KAffix > should work with \`position\` prop 1`] = `"<head></head><body style=\\"height: 100px; overflow: auto;\\"><div class=\\"k-affix k-affix--absolute-positioned\\"></div></body>"`; | ||
exports[`Test: KAffix > should work with \`top\` prop 1`] = `"<head></head><body style=\\"height: 100px; overflow: auto;\\"><div class=\\"k-affix k-affix--affixed k-affix--test\\" style=\\"top: 120px;\\"></div></body>"`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { afterEach, expect, test, describe, beforeEach, vi } from 'vitest'; | ||
import KAffix from '../src'; | ||
import { tick } from 'svelte'; | ||
|
||
const initHost = () => { | ||
document.body.style.height = '100px'; | ||
document.body.style.overflow = 'auto'; | ||
}; | ||
|
||
beforeEach(() => { | ||
initHost(); | ||
vi.useFakeTimers(); | ||
}); | ||
afterEach(() => { | ||
document.body.innerHTML = ''; | ||
vi.restoreAllMocks(); | ||
}); | ||
|
||
describe('Test: KAffix', () => { | ||
vi.mock('svelte', async () => { | ||
const actual = (await vi.importActual('svelte')) as object; | ||
return { | ||
...actual, | ||
// @ts-ignore | ||
onMount: (await import('svelte/internal')).onMount | ||
}; | ||
}); | ||
|
||
test('props: cls', async () => { | ||
const instance = new KAffix({ | ||
target: document.body, | ||
props: { | ||
cls: 'k-affix--test' | ||
} | ||
}); | ||
expect(instance).toBeTruthy(); | ||
expect( | ||
(document.documentElement as HTMLElement)!.innerHTML.includes('k-affix--test') | ||
).toBeTruthy(); | ||
expect(document.documentElement.innerHTML).matchSnapshot(); | ||
}); | ||
|
||
test('should work with `top` prop', async () => { | ||
const instance = new KAffix({ | ||
target: document.body, | ||
props: { | ||
cls: 'k-affix--test', | ||
top: 120 | ||
} | ||
}); | ||
expect(instance).toBeTruthy(); | ||
expect( | ||
(document.documentElement as HTMLElement)!.innerHTML.includes('k-affix--affixed') | ||
).not.toBeTruthy(); | ||
document.documentElement.scrollTop = 200; | ||
document.documentElement.dispatchEvent(new Event('scroll', { bubbles: true })); | ||
await tick(); | ||
await vi.advanceTimersByTimeAsync(300); | ||
expect(document.body.innerHTML).toContain('top: 120px;'); | ||
expect(document.documentElement.innerHTML).matchSnapshot(); | ||
}); | ||
|
||
test('should work with `position` prop', async () => { | ||
const instance = new KAffix({ | ||
target: document.body, | ||
props: { | ||
positionOption: 'absolute' | ||
} | ||
}); | ||
expect(instance).toBeTruthy(); | ||
expect( | ||
(document.documentElement as HTMLElement)!.innerHTML.includes('k-affix--absolute-positioned') | ||
).toBeTruthy(); | ||
expect(document.documentElement.innerHTML).matchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{ | ||
"name": "@ikun-ui/affix", | ||
"version": "0.0.16", | ||
"type": "module", | ||
"main": "src/index.ts", | ||
"types": "src/index.d.ts", | ||
"svelte": "src/index.ts", | ||
"keywords": [ | ||
"svelte", | ||
"svelte3", | ||
"web component", | ||
"component", | ||
"react", | ||
"vue", | ||
"svelte-kit", | ||
"dx" | ||
], | ||
"files": [ | ||
"dist", | ||
"package.json" | ||
], | ||
"scripts": { | ||
"build": "npm run build:js && npm run build:svelte", | ||
"build:js": "tsc -p . --outDir dist/ --rootDir src/", | ||
"build:svelte": "svelte-strip strip src/ dist", | ||
"publish:pre": "node ../../scripts/pre-publish.js", | ||
"publish:npm": "pnpm run publish:pre && pnpm publish --no-git-checks --access public" | ||
}, | ||
"publishConfig": { | ||
"access": "public", | ||
"main": "dist/index.js", | ||
"module": "dist/index.js", | ||
"svelte": "dist/index.js", | ||
"types": "dist/index.d.ts" | ||
}, | ||
"dependencies": { | ||
"@ikun-ui/icon": "workspace:*", | ||
"@ikun-ui/utils": "workspace:*", | ||
"baiwusanyu-utils": "^1.0.16", | ||
"clsx": "^2.0.0" | ||
}, | ||
"devDependencies": { | ||
"@tsconfig/svelte": "^5.0.2", | ||
"svelte-strip": "^2.0.0", | ||
"tslib": "^2.6.2", | ||
"typescript": "^5.3.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
<script lang="ts"> | ||
import { getPrefixCls } from '@ikun-ui/utils'; | ||
import { clsx } from 'clsx'; | ||
import type { KAffixProps } from './types'; | ||
import type { ScrollTarget } from './utils'; | ||
import { unwrapElement, getScrollTop, getRect, beforeNextFrameOnce } from './utils'; | ||
import { onDestroy, onMount } from 'svelte'; | ||
export let cls: KAffixProps['cls'] = undefined; | ||
export let attrs: KAffixProps['attrs'] = {}; | ||
export let listenTo: KAffixProps['listenTo'] = undefined; | ||
export let top: KAffixProps['top'] = undefined; | ||
export let bottom: KAffixProps['bottom'] = undefined; | ||
export let triggerTop: KAffixProps['triggerTop'] = undefined; | ||
export let triggerBottom: KAffixProps['triggerBottom'] = undefined; | ||
export let positionOption: KAffixProps['positionOption'] = 'fixed'; | ||
let scrollTarget: ScrollTarget | null = null; | ||
let stickToTopRef = false; | ||
let stickToBottomRef = false; | ||
let bottomAffixedTriggerScrollTopRef: number | null = null; | ||
let topAffixedTriggerScrollTopRef: number | null = null; | ||
$: affixedRef = stickToBottomRef || stickToTopRef; | ||
$: mergedOffsetTopRef = triggerTop ?? top; | ||
$: mergedTopRef = top ?? triggerTop; | ||
$: mergedBottomRef = bottom ?? triggerBottom; | ||
$: mergedOffsetBottomRef = triggerBottom ?? bottom; | ||
let selfRef: Element | null = null; | ||
const init = (): void => { | ||
if (listenTo) { | ||
scrollTarget = unwrapElement(listenTo); | ||
} else { | ||
scrollTarget = document; | ||
} | ||
if (scrollTarget) { | ||
scrollTarget.addEventListener('scroll', handleScroll); | ||
handleScroll(); | ||
} | ||
}; | ||
function handleScroll(): void { | ||
beforeNextFrameOnce(doHandleScroll); | ||
} | ||
function doHandleScroll(): void { | ||
const selfEl = selfRef; | ||
if (!scrollTarget || !selfEl) return; | ||
const scrollTop = getScrollTop(scrollTarget); | ||
if (affixedRef) { | ||
if (topAffixedTriggerScrollTopRef !== null && scrollTop < topAffixedTriggerScrollTopRef) { | ||
stickToTopRef = false; | ||
topAffixedTriggerScrollTopRef = null; | ||
} | ||
if ( | ||
bottomAffixedTriggerScrollTopRef !== null && | ||
scrollTop > bottomAffixedTriggerScrollTopRef | ||
) { | ||
stickToBottomRef = false; | ||
bottomAffixedTriggerScrollTopRef = null; | ||
} | ||
return; | ||
} | ||
const containerRect = getRect(scrollTarget); | ||
const affixRect = selfEl.getBoundingClientRect(); | ||
const pxToTop = affixRect.top - containerRect.top; | ||
const pxToBottom = containerRect.bottom - affixRect.bottom; | ||
const mergedOffsetTop = mergedOffsetTopRef; | ||
const mergedOffsetBottom = mergedOffsetBottomRef; | ||
if (mergedOffsetTop !== undefined && pxToTop <= mergedOffsetTop) { | ||
stickToTopRef = true; | ||
topAffixedTriggerScrollTopRef = scrollTop - (mergedOffsetTop - pxToTop); | ||
} else { | ||
stickToTopRef = false; | ||
topAffixedTriggerScrollTopRef = null; | ||
} | ||
if (mergedOffsetBottom !== undefined && pxToBottom <= mergedOffsetBottom) { | ||
stickToBottomRef = true; | ||
bottomAffixedTriggerScrollTopRef = scrollTop + mergedOffsetBottom - pxToBottom; | ||
} else { | ||
stickToBottomRef = false; | ||
bottomAffixedTriggerScrollTopRef = null; | ||
} | ||
} | ||
onMount(init); | ||
onDestroy(() => { | ||
if (!scrollTarget) return; | ||
scrollTarget.removeEventListener('scroll', handleScroll); | ||
}); | ||
let styleTop: string = ''; | ||
$: { | ||
if (stickToTopRef && mergedOffsetTopRef !== undefined && mergedTopRef !== undefined) { | ||
styleTop = `${mergedTopRef}px`; | ||
} | ||
} | ||
let styleBottom: string = ''; | ||
$: { | ||
if (stickToBottomRef && mergedOffsetBottomRef !== undefined && mergedBottomRef !== undefined) { | ||
styleBottom = `${mergedBottomRef}px`; | ||
} | ||
} | ||
const prefixCls = getPrefixCls('affix'); | ||
$: cnames = clsx( | ||
prefixCls, | ||
{ | ||
[`${prefixCls}--affixed`]: affixedRef, | ||
[`${prefixCls}--absolute-positioned`]: positionOption === 'absolute' | ||
}, | ||
cls | ||
); | ||
</script> | ||
|
||
<div | ||
class={cnames} | ||
style:top={styleTop} | ||
style:bottom={styleBottom} | ||
bind:this={selfRef} | ||
{...$$restProps} | ||
{...attrs} | ||
> | ||
<slot /> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/// <reference types="./types" /> | ||
import Affix from './index.svelte'; | ||
export { Affix as KAffix }; | ||
|
||
export default Affix; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/// <reference types="svelte" /> | ||
import type { ClassValue } from 'clsx'; | ||
import type { ScrollTarget } from './utils'; | ||
export type KAffixProps = { | ||
listenTo: string | ScrollTarget | (() => HTMLElement) | undefined; | ||
top: number | undefined; | ||
bottom: number | undefined; | ||
triggerTop: number | undefined; | ||
triggerBottom: number | undefined; | ||
positionOption: 'fixed' | 'absolute'; | ||
cls: ClassValue; | ||
attrs: Record<string, string>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import type { IKunUncertainFunction } from '@ikun-ui/utils'; | ||
|
||
export type ScrollTarget = Window | Document | HTMLElement; | ||
|
||
export function getScrollTop(target: ScrollTarget): number { | ||
return target instanceof HTMLElement ? target.scrollTop : window.scrollY; | ||
} | ||
|
||
export function getRect(target: ScrollTarget): { top: number; bottom: number } { | ||
return target instanceof HTMLElement | ||
? target.getBoundingClientRect() | ||
: { top: 0, bottom: window.innerHeight }; | ||
} | ||
|
||
type GetElement = () => HTMLElement; | ||
|
||
function unwrapElement<T>( | ||
target: T | string | GetElement | ||
): T extends HTMLElement ? HTMLElement : HTMLElement | null; | ||
function unwrapElement(target: HTMLElement | string | GetElement) { | ||
if (typeof target === 'string') return document.querySelector(target); | ||
if (typeof target === 'function') return target(); | ||
return target; | ||
} | ||
|
||
export { unwrapElement }; | ||
|
||
let onceCbs: IKunUncertainFunction[] = []; | ||
const paramsMap: WeakMap<IKunUncertainFunction, any[]> = new WeakMap(); | ||
|
||
function flushOnceCallbacks(): void { | ||
// @ts-ignore | ||
onceCbs.forEach((cb) => cb(...paramsMap.get(cb)!)); | ||
onceCbs = []; | ||
} | ||
|
||
function beforeNextFrameOnce(cb: IKunUncertainFunction, ...params: any[]): void { | ||
paramsMap.set(cb, params); | ||
if (onceCbs.includes(cb)) return; | ||
onceCbs.push(cb) === 1 && requestAnimationFrame(flushOnceCallbacks); | ||
} | ||
export { beforeNextFrameOnce }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"extends": "@tsconfig/svelte/tsconfig.json", | ||
|
||
"compilerOptions": { | ||
"noImplicitAny": true, | ||
"strict": true, | ||
"declaration": true | ||
}, | ||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.svelte"], | ||
"exclude": ["node_modules/*", "**/*.spec.ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.