diff --git a/components/lib/api/PrimeReactContext.js b/components/lib/api/PrimeReactContext.js
index be605deccb..75b5998a39 100644
--- a/components/lib/api/PrimeReactContext.js
+++ b/components/lib/api/PrimeReactContext.js
@@ -21,6 +21,7 @@ export const PrimeReactProvider = (props) => {
toast: 1200
});
const [pt, setPt] = useState(undefined);
+ const [unstyled, setUnstyled] = useState(false);
const [filterMatchModeOptions, setFilterMatchModeOptions] = useState({
text: [FilterMatchMode.STARTS_WITH, FilterMatchMode.CONTAINS, FilterMatchMode.NOT_CONTAINS, FilterMatchMode.ENDS_WITH, FilterMatchMode.EQUALS, FilterMatchMode.NOT_EQUALS],
numeric: [FilterMatchMode.EQUALS, FilterMatchMode.NOT_EQUALS, FilterMatchMode.LESS_THAN, FilterMatchMode.LESS_THAN_OR_EQUAL_TO, FilterMatchMode.GREATER_THAN, FilterMatchMode.GREATER_THAN_OR_EQUAL_TO],
@@ -70,7 +71,9 @@ export const PrimeReactProvider = (props) => {
pt,
setPt,
filterMatchModeOptions,
- setFilterMatchModeOptions
+ setFilterMatchModeOptions,
+ unstyled,
+ setUnstyled
};
return {props.children};
diff --git a/components/lib/api/api.d.ts b/components/lib/api/api.d.ts
index 778aa9a4f5..b5a89cd176 100644
--- a/components/lib/api/api.d.ts
+++ b/components/lib/api/api.d.ts
@@ -206,6 +206,7 @@ export interface APIOptions {
* This option allows to direct implementation of all relevant attributes (e.g., style, classnames) within the respective HTML tag.
*/
pt?: PrimeReactPTOptions;
+ unstyled?: boolean;
/**
* This method is used to change the theme dynamically.
* @param {string} theme - The name of the theme to be applied.
diff --git a/components/lib/componentbase/ComponentBase.js b/components/lib/componentbase/ComponentBase.js
index 16eddd9453..4897ca4dae 100644
--- a/components/lib/componentbase/ComponentBase.js
+++ b/components/lib/componentbase/ComponentBase.js
@@ -3,11 +3,27 @@ import { ObjectUtils } from '../utils/Utils';
export const ComponentBase = {
defaultProps: {
- pt: undefined
+ pt: undefined,
+ unstyled: false
},
context: undefined,
+ classes: {},
+ styles: "",
extend: (props = {}) => {
+ const css = props.css;
const defaultProps = { ...props.defaultProps, ...ComponentBase.defaultProps };
+ const inlineStyles = {
+ hiddenAccessible: {
+ border: '0',
+ clip: 'rect(0 0 0 0)',
+ height: '1px',
+ margin: '-1px',
+ overflow: 'hidden',
+ padding: '0',
+ position: 'absolute',
+ width: '1px'
+ }
+ };
const getProps = (props, context = {}) => {
ComponentBase.context = context;
@@ -29,7 +45,6 @@ export const ComponentBase = {
const datasetPrefix = 'data-pc-';
const componentName = (params.props && params.props.__TYPE && ObjectUtils.convertToFlatCase(params.props.__TYPE)) || '';
const pt = ComponentBase.context.pt || PrimeReact.pt || {};
-
const defaultPT = (key) => pt && getOptionValue(pt[componentName], key);
const self = ObjectUtils.getPropValue(obj, key, params)[key];
const globalPT = defaultPT(key);
@@ -53,10 +68,33 @@ export const ComponentBase = {
};
const setMetaData = (metadata = {}) => {
- const ptm = (key = '', params = {}) => ptmo((metadata.props || {}).pt, key, { ...metadata, ...params });
+ const { props, state } = metadata;
+ const ptm = (key = '', params = {}) => ptmo((props || {}).pt, key, { ...metadata, ...params });
const ptmo = (obj = {}, key = '', params = {}) => getPTValue(obj, key, params);
- return { ptm, ptmo };
+ const isUnstyled = () => {
+ return ComponentBase.context.unstyled || PrimeReact.unstyled || props.unstyled;
+ };
+
+ const cx = (key = '', params = {}) => {
+ return !isUnstyled() ? getOptionValue(css && css.classes, key, { props, state, ...params }) : undefined;
+ };
+
+ const sx = (key = '', when = true, params = {}) => {
+ if (when) {
+ const self = getOptionValue(css && css.inlineStyles, key, { props, state, ...params });
+ const base = getOptionValue(inlineStyles, key, { props: props || {}, state, ...params });
+ let merged = {
+ ...ObjectUtils.getMergedProps(base, self)
+ };
+
+ return merged;
+ }
+
+ return undefined;
+ }
+
+ return { ptm, ptmo, sx, cx };
};
return {
diff --git a/components/lib/hooks/Hooks.js b/components/lib/hooks/Hooks.js
index b4305c05d8..28e6b2875c 100644
--- a/components/lib/hooks/Hooks.js
+++ b/components/lib/hooks/Hooks.js
@@ -17,6 +17,7 @@ import { useLocalStorage, useSessionStorage, useStorage } from './useStorage';
import { useTimeout } from './useTimeout';
import { useUnmountEffect } from './useUnmountEffect';
import { useUpdateEffect } from './useUpdateEffect';
+import { useStyle } from './useStyle';
export {
usePrevious,
@@ -30,6 +31,7 @@ export {
useIntersectionObserver,
useInterval,
useStorage,
+ useStyle,
useLocalStorage,
useSessionStorage,
useTimeout,
diff --git a/components/lib/hooks/hooks.d.ts b/components/lib/hooks/hooks.d.ts
index aeda7abfcc..84cc4c2a08 100644
--- a/components/lib/hooks/hooks.d.ts
+++ b/components/lib/hooks/hooks.d.ts
@@ -21,6 +21,45 @@ interface MousePositionOptions {
y: number;
}
+/**
+ * Custom UseStyleOptions
+ */
+interface UseStyleOptions {
+ document?: Document;
+ immediate: boolean;
+ manual: boolean;
+ name: string;
+ media: string;
+}
+
+/**
+ * Custom StyleOptions
+ */
+interface StyleOptions {
+ /**
+ * Defines data-pc-name attribute of the style tag.
+ */
+ name: string;
+ /**
+ * The css object.
+ */
+ css: React.RefObject;
+ /**
+ * This option is used to load the style tag by the name.
+ * @returns {void}
+ */
+ load: () => void;
+ /**
+ * This method is used to remove the style tag from the head.
+ * @returns {void}
+ */
+ unload: () => void;
+ /**
+ * Whether the style is loaded or not.
+ */
+ isLoaded: boolean;
+}
+
/**
* Custom MouseDataOptions
*/
@@ -220,6 +259,13 @@ export declare function useMouse(): MouseDataOptions;
* @param {MousePositionOptions} initialValue - The initial value.
*/
export declare function useMove(mode: 'horizontal' | 'vertical' | 'both', initialValue: MousePositionOptions): MouseMoveOptions;
+
+/**
+ * Custom hook to use to get style options.
+ * @param {string} css - The style text content.
+ * @param {UseStyleOptions} options - The options of the style.
+ */
+export declare function useStyle(css: string, options?: UseStyleOptions): StyleOptions;
/**
* Custom hook to use change the current favicon.
* @param {string} newIcon - The new favicon url to set.
diff --git a/components/lib/hooks/useStyle.js b/components/lib/hooks/useStyle.js
new file mode 100644
index 0000000000..d883d00aa2
--- /dev/null
+++ b/components/lib/hooks/useStyle.js
@@ -0,0 +1,59 @@
+import { useEffect, useRef, useState } from 'react';
+import { DomHandler, ObjectUtils } from "../utils/Utils";
+
+let _id = 0;
+
+export const useStyle = (css = {}, options = {}) => {
+ const [isLoaded, setIsLoaded] = useState(false);
+
+ const cssRef = useRef(null);
+ const defaultDocument = DomHandler.isClient() ? window.document : undefined;
+ const { document = defaultDocument, immediate = true, manual = false, name = `primereact_style_${++_id}`, media } = options;
+
+ useEffect(() => {
+ cssRef.current = css;
+ }, [css]);
+
+ const load = () => {
+ if (!document) return;
+
+ const el = document.querySelector(`[data-pc-name="${name}"]`) || document.createElement('style');
+
+ if (ObjectUtils.isNotEmpty(el) || !el.isConnected) {
+ el.type = 'text/css';
+ el.setAttribute('data-pc-name', name);
+ if (media) el.media = media;
+ document.head.appendChild(el);
+ }
+
+ if (isLoaded) return;
+
+ el.textContent = cssRef.current;
+ setIsLoaded(true);
+ };
+
+ const unload = () => {
+ if (!document || !isLoaded) return;
+ const node = document.querySelector(`[data-pc-name="${name}"]`);
+
+ if (node && node.isConnected) {
+ document.head.removeChild(node);
+ setIsLoaded(false);
+ }
+ };
+
+ useEffect(() => {
+ if (immediate && !manual) load();
+
+ return () => unload();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [immediate, manual]);
+
+ return {
+ name,
+ css: cssRef,
+ unload,
+ load,
+ isLoaded
+ };
+}
diff --git a/components/lib/utils/DomHandler.js b/components/lib/utils/DomHandler.js
index 82f65670f6..64e1364bd3 100644
--- a/components/lib/utils/DomHandler.js
+++ b/components/lib/utils/DomHandler.js
@@ -669,6 +669,10 @@ export default class DomHandler {
return /(chrome)/i.test(navigator.userAgent);
}
+ static isClient() {
+ return !!(typeof window !== 'undefined' && window.document && window.document.createElement);
+ }
+
static isTouchDevice() {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
}
diff --git a/components/lib/utils/utils.d.ts b/components/lib/utils/utils.d.ts
index 13232cbd7a..fac45ac245 100644
--- a/components/lib/utils/utils.d.ts
+++ b/components/lib/utils/utils.d.ts
@@ -59,6 +59,7 @@ export declare class DomHandler {
static getUserAgent(): string;
static isIOS(): boolean;
static isAndroid(): boolean;
+ static isClient(): boolean;
static isTouchDevice(): boolean;
static isFunction(obj: any): boolean;
static appendChild(el: HTMLElement, target: HTMLElement): void;