You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
6.5 KiB
196 lines
6.5 KiB
import type { ShallowRef, ExtractPropTypes, InjectionKey, Ref } from 'vue';
|
|
import {
|
|
provide,
|
|
defineComponent,
|
|
unref,
|
|
inject,
|
|
watch,
|
|
shallowRef,
|
|
getCurrentInstance,
|
|
} from 'vue';
|
|
import CacheEntity from './Cache';
|
|
import type { Linter } from './linters/interface';
|
|
import type { Transformer } from './transformers/interface';
|
|
import { arrayType, booleanType, objectType, someType, stringType, withInstall } from '../type';
|
|
export const ATTR_TOKEN = 'data-token-hash';
|
|
export const ATTR_MARK = 'data-css-hash';
|
|
export const ATTR_CACHE_PATH = 'data-cache-path';
|
|
|
|
// Mark css-in-js instance in style element
|
|
export const CSS_IN_JS_INSTANCE = '__cssinjs_instance__';
|
|
|
|
export function createCache() {
|
|
const cssinjsInstanceId = Math.random().toString(12).slice(2);
|
|
|
|
// Tricky SSR: Move all inline style to the head.
|
|
// PS: We do not recommend tricky mode.
|
|
if (typeof document !== 'undefined' && document.head && document.body) {
|
|
const styles = document.body.querySelectorAll(`style[${ATTR_MARK}]`) || [];
|
|
const { firstChild } = document.head;
|
|
|
|
Array.from(styles).forEach(style => {
|
|
(style as any)[CSS_IN_JS_INSTANCE] = (style as any)[CSS_IN_JS_INSTANCE] || cssinjsInstanceId;
|
|
|
|
// Not force move if no head
|
|
// Not force move if no head
|
|
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
|
|
document.head.insertBefore(style, firstChild);
|
|
}
|
|
});
|
|
|
|
// Deduplicate of moved styles
|
|
const styleHash: Record<string, boolean> = {};
|
|
Array.from(document.querySelectorAll(`style[${ATTR_MARK}]`)).forEach(style => {
|
|
const hash = style.getAttribute(ATTR_MARK)!;
|
|
if (styleHash[hash]) {
|
|
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
|
|
style.parentNode?.removeChild(style);
|
|
}
|
|
} else {
|
|
styleHash[hash] = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
return new CacheEntity(cssinjsInstanceId);
|
|
}
|
|
|
|
export type HashPriority = 'low' | 'high';
|
|
|
|
export interface StyleContextProps {
|
|
autoClear?: boolean;
|
|
/** @private Test only. Not work in production. */
|
|
mock?: 'server' | 'client';
|
|
/**
|
|
* Only set when you need ssr to extract style on you own.
|
|
* If not provided, it will auto create <style /> on the end of Provider in server side.
|
|
*/
|
|
cache: CacheEntity;
|
|
/** Tell children that this context is default generated context */
|
|
defaultCache: boolean;
|
|
/** Use `:where` selector to reduce hashId css selector priority */
|
|
hashPriority?: HashPriority;
|
|
/** Tell cssinjs where to inject style in */
|
|
container?: Element | ShadowRoot;
|
|
/** Component wil render inline `<style />` for fallback in SSR. Not recommend. */
|
|
ssrInline?: boolean;
|
|
/** Transform css before inject in document. Please note that `transformers` do not support dynamic update */
|
|
transformers?: Transformer[];
|
|
/**
|
|
* Linters to lint css before inject in document.
|
|
* Styles will be linted after transforming.
|
|
* Please note that `linters` do not support dynamic update.
|
|
*/
|
|
linters?: Linter[];
|
|
}
|
|
|
|
const StyleContextKey: InjectionKey<ShallowRef<Partial<StyleContextProps>>> =
|
|
Symbol('StyleContextKey');
|
|
|
|
export type UseStyleProviderProps = Partial<StyleContextProps> | Ref<Partial<StyleContextProps>>;
|
|
|
|
// fix: https://github.com/vueComponent/ant-design-vue/issues/7023
|
|
const getCache = () => {
|
|
const instance = getCurrentInstance();
|
|
let cache: CacheEntity;
|
|
if (instance && instance.appContext) {
|
|
const globalCache = instance.appContext?.config?.globalProperties?.__ANTDV_CSSINJS_CACHE__;
|
|
if (globalCache) {
|
|
cache = globalCache;
|
|
} else {
|
|
cache = createCache();
|
|
if (instance.appContext.config.globalProperties) {
|
|
instance.appContext.config.globalProperties.__ANTDV_CSSINJS_CACHE__ = cache;
|
|
}
|
|
}
|
|
} else {
|
|
cache = createCache();
|
|
}
|
|
return cache;
|
|
};
|
|
|
|
const defaultStyleContext: StyleContextProps = {
|
|
cache: createCache(),
|
|
defaultCache: true,
|
|
hashPriority: 'low',
|
|
};
|
|
// fix: https://github.com/vueComponent/ant-design-vue/issues/6912
|
|
export const useStyleInject = () => {
|
|
const cache = getCache();
|
|
return inject(StyleContextKey, shallowRef({ ...defaultStyleContext, cache }));
|
|
};
|
|
export const useStyleProvider = (props: UseStyleProviderProps) => {
|
|
const parentContext = useStyleInject();
|
|
const context = shallowRef<Partial<StyleContextProps>>({
|
|
...defaultStyleContext,
|
|
cache: createCache(),
|
|
});
|
|
watch(
|
|
[() => unref(props), parentContext],
|
|
() => {
|
|
const mergedContext: Partial<StyleContextProps> = {
|
|
...parentContext.value,
|
|
};
|
|
const propsValue = unref(props);
|
|
Object.keys(propsValue).forEach(key => {
|
|
const value = propsValue[key];
|
|
if (propsValue[key] !== undefined) {
|
|
mergedContext[key] = value;
|
|
}
|
|
});
|
|
|
|
const { cache } = propsValue;
|
|
mergedContext.cache = mergedContext.cache || createCache();
|
|
mergedContext.defaultCache = !cache && parentContext.value.defaultCache;
|
|
context.value = mergedContext;
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
provide(StyleContextKey, context);
|
|
return context;
|
|
};
|
|
export const styleProviderProps = () => ({
|
|
autoClear: booleanType(),
|
|
/** @private Test only. Not work in production. */
|
|
mock: stringType<'server' | 'client'>(),
|
|
/**
|
|
* Only set when you need ssr to extract style on you own.
|
|
* If not provided, it will auto create <style /> on the end of Provider in server side.
|
|
*/
|
|
cache: objectType<CacheEntity>(),
|
|
/** Tell children that this context is default generated context */
|
|
defaultCache: booleanType(),
|
|
/** Use `:where` selector to reduce hashId css selector priority */
|
|
hashPriority: stringType<HashPriority>(),
|
|
/** Tell cssinjs where to inject style in */
|
|
container: someType<Element | ShadowRoot>(),
|
|
/** Component wil render inline `<style />` for fallback in SSR. Not recommend. */
|
|
ssrInline: booleanType(),
|
|
/** Transform css before inject in document. Please note that `transformers` do not support dynamic update */
|
|
transformers: arrayType<Transformer[]>(),
|
|
/**
|
|
* Linters to lint css before inject in document.
|
|
* Styles will be linted after transforming.
|
|
* Please note that `linters` do not support dynamic update.
|
|
*/
|
|
linters: arrayType<Linter[]>(),
|
|
});
|
|
export type StyleProviderProps = Partial<ExtractPropTypes<ReturnType<typeof styleProviderProps>>>;
|
|
export const StyleProvider = withInstall(
|
|
defineComponent({
|
|
name: 'AStyleProvider',
|
|
inheritAttrs: false,
|
|
props: styleProviderProps(),
|
|
setup(props, { slots }) {
|
|
useStyleProvider(props);
|
|
return () => slots.default?.();
|
|
},
|
|
}),
|
|
);
|
|
|
|
export default {
|
|
useStyleInject,
|
|
useStyleProvider,
|
|
StyleProvider,
|
|
};
|