1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

fix: Don't crash when hovering hotbar menu index

- Remove all uses of React.ReactNode as it is unsafe, replace them with
  usages of SafeReactNode which doesn't contain the '{}' type

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-05-09 15:45:37 -04:00
parent f13a145a27
commit 9d51ef2aa6
84 changed files with 246 additions and 208 deletions

13
package-lock.json generated
View File

@ -36625,6 +36625,7 @@
"devDependencies": { "devDependencies": {
"@async-fn/jest": "^1.6.4", "@async-fn/jest": "^1.6.4",
"@k8slens/typescript": "^6.5.0-alpha.2", "@k8slens/typescript": "^6.5.0-alpha.2",
"@ogre-tools/injectable-extension-for-auto-registration": "^15.9.0",
"@ogre-tools/test-utils": "^15.8.1", "@ogre-tools/test-utils": "^15.8.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"webpack-node-externals": "^3.0.0" "webpack-node-externals": "^3.0.0"
@ -36635,6 +36636,17 @@
"lodash": "^4.17.21" "lodash": "^4.17.21"
} }
}, },
"packages/infrastructure/webpack/node_modules/@ogre-tools/injectable-extension-for-auto-registration": {
"version": "15.9.0",
"resolved": "https://registry.npmjs.org/@ogre-tools/injectable-extension-for-auto-registration/-/injectable-extension-for-auto-registration-15.9.0.tgz",
"integrity": "sha512-5Ik43ZLAOhBODrhF/MbTkC3SItFMNxibufBoYFqCERfHSHyZE6pUa5yMbjrh+YFkntIrDH2IScW7BqZkraHvTA==",
"dev": true,
"peerDependencies": {
"@ogre-tools/fp": "*",
"@ogre-tools/injectable": "*",
"lodash": "^4.17.21"
}
},
"packages/infrastructure/webpack/node_modules/sass-loader": { "packages/infrastructure/webpack/node_modules/sass-loader": {
"version": "13.2.2", "version": "13.2.2",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.2.tgz", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.2.tgz",
@ -36869,6 +36881,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@k8slens/kube-object": "^1.0.0-alpha.1", "@k8slens/kube-object": "^1.0.0-alpha.1",
"@k8slens/utilities": "^1.0.0-alpha.3",
"@ogre-tools/injectable": "^15.8.1", "@ogre-tools/injectable": "^15.8.1",
"react": "^17.0.2" "react": "^17.0.2"
} }

View File

@ -1,10 +1,11 @@
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import invokeShortcutInjectable, { InvokeShortcut } from "./invoke-shortcut.injectable"; import invokeShortcutInjectable, { InvokeShortcut } from "./invoke-shortcut.injectable";
export interface KeyboardShortcutListenerProps { export interface KeyboardShortcutListenerProps {
children: React.ReactNode; children: SingleOrMany<SafeReactNode>;
} }
interface Dependencies { interface Dependencies {

View File

@ -1,8 +1,9 @@
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import React from "react"; import React from "react";
export interface KeyboardShortcutScopeProps { export interface KeyboardShortcutScopeProps {
id: string; id: string;
children: React.ReactNode; children: SingleOrMany<SafeReactNode>;
} }
export const KeyboardShortcutScope = ({ id, children }: KeyboardShortcutScopeProps) => ( export const KeyboardShortcutScope = ({ id, children }: KeyboardShortcutScopeProps) => (

View File

@ -7,7 +7,7 @@ import EventEmitter from "events";
import type TypedEmitter from "typed-emitter"; import type TypedEmitter from "typed-emitter";
import { observable, makeObservable } from "mobx"; import { observable, makeObservable } from "mobx";
import { once } from "lodash"; import { once } from "lodash";
import type { Disposer } from "@k8slens/utilities"; import type { Disposer, SafeReactNode } from "@k8slens/utilities";
import { iter } from "@k8slens/utilities"; import { iter } from "@k8slens/utilities";
import type { CategoryColumnRegistration, TitleCellProps } from "../../renderer/components/catalog/custom-category-columns"; import type { CategoryColumnRegistration, TitleCellProps } from "../../renderer/components/catalog/custom-category-columns";
@ -201,7 +201,7 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm
* Defaults to no badge. * Defaults to no badge.
* The badge is displayed next to the Category name in the Catalog Category menu * The badge is displayed next to the Category name in the Catalog Category menu
*/ */
public getBadge(): React.ReactNode { public getBadge(): SafeReactNode {
return null; return null;
} }

View File

@ -6,6 +6,7 @@
import "./add-remove-buttons.scss"; import "./add-remove-buttons.scss";
import React from "react"; import React from "react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { Button } from "@k8slens/button"; import { Button } from "@k8slens/button";
import { Icon } from "../icon"; import { Icon } from "../icon";
@ -13,8 +14,8 @@ import { Icon } from "../icon";
export interface AddRemoveButtonsProps extends React.HTMLAttributes<any> { export interface AddRemoveButtonsProps extends React.HTMLAttributes<any> {
onAdd?: () => void; onAdd?: () => void;
onRemove?: () => void; onRemove?: () => void;
addTooltip?: React.ReactNode; addTooltip?: SafeReactNode;
removeTooltip?: React.ReactNode; removeTooltip?: SafeReactNode;
} }
export class AddRemoveButtons extends React.PureComponent<AddRemoveButtonsProps> { export class AddRemoveButtons extends React.PureComponent<AddRemoveButtonsProps> {

View File

@ -5,6 +5,7 @@
import "./animate.scss"; import "./animate.scss";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames, noop } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import type { RequestAnimationFrame } from "./request-animation-frame.injectable"; import type { RequestAnimationFrame } from "./request-animation-frame.injectable";
@ -21,7 +22,7 @@ export interface AnimateProps {
onLeave?: () => void; onLeave?: () => void;
enterDuration?: number; enterDuration?: number;
leaveDuration?: number; leaveDuration?: number;
children?: React.ReactNode; children?: SingleOrMany<SafeReactNode>;
} }
interface Dependencies { interface Dependencies {

View File

@ -8,7 +8,7 @@ import styles from "./avatar.module.scss";
import type { ImgHTMLAttributes, MouseEventHandler } from "react"; import type { ImgHTMLAttributes, MouseEventHandler } from "react";
import React from "react"; import React from "react";
import randomColor from "randomcolor"; import randomColor from "randomcolor";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { computeDefaultShortName } from "../../../common/catalog/helpers"; import { computeDefaultShortName } from "../../../common/catalog/helpers";
@ -21,7 +21,7 @@ export interface AvatarProps {
variant?: "circle" | "rounded" | "square"; variant?: "circle" | "rounded" | "square";
imgProps?: ImgHTMLAttributes<HTMLImageElement>; imgProps?: ImgHTMLAttributes<HTMLImageElement>;
disabled?: boolean; disabled?: boolean;
children?: SingleOrMany<React.ReactNode>; children?: SingleOrMany<SafeReactNode>;
className?: string; className?: string;
id?: string; id?: string;
onClick?: MouseEventHandler<HTMLDivElement>; onClick?: MouseEventHandler<HTMLDivElement>;

View File

@ -8,16 +8,18 @@ import styles from "./badge.module.scss";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { action, observable } from "mobx"; import { action, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { withTooltip } from "@k8slens/tooltip"; import { withTooltip } from "@k8slens/tooltip";
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> { export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
small?: boolean; small?: boolean;
flat?: boolean; flat?: boolean;
label?: React.ReactNode; label?: SafeReactNode;
expandable?: boolean; expandable?: boolean;
disabled?: boolean; disabled?: boolean;
scrollable?: boolean; scrollable?: boolean;
children?: SingleOrMany<SafeReactNode>;
} }
// Common handler for all Badge instances // Common handler for all Badge instances

View File

@ -3,9 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { SafeReactNode } from "@k8slens/utilities";
import type { DiContainer } from "@ogre-tools/injectable"; import type { DiContainer } from "@ogre-tools/injectable";
import { computed } from "mobx"; import { computed } from "mobx";
import type React from "react";
import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable"; import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
@ -20,8 +20,8 @@ describe("Custom Category Views", () => {
}); });
it("should order items correctly over all extensions", () => { it("should order items correctly over all extensions", () => {
const component1 = (): React.ReactNode => null; const component1 = (): SafeReactNode => null;
const component2 = (): React.ReactNode => null; const component2 = (): SafeReactNode => null;
di.override(rendererExtensionsInjectable, () => computed(() => [ di.override(rendererExtensionsInjectable, () => computed(() => [
{ {
@ -58,8 +58,8 @@ describe("Custom Category Views", () => {
}); });
it("should put put priority < 50 items in before", () => { it("should put put priority < 50 items in before", () => {
const component1 = (): React.ReactNode => null; const component1 = (): SafeReactNode => null;
const component2 = (): React.ReactNode => null; const component2 = (): SafeReactNode => null;
di.override(rendererExtensionsInjectable, () => computed(() => [ di.override(rendererExtensionsInjectable, () => computed(() => [
{ {

View File

@ -2,6 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { SafeReactNode } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { orderBy } from "lodash"; import { orderBy } from "lodash";
import type { CatalogCategory, CatalogEntity } from "../../../../common/catalog"; import type { CatalogCategory, CatalogEntity } from "../../../../common/catalog";
@ -35,7 +36,7 @@ const getCategoryColumnsInjectable = getInjectable({
const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {}; const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {};
const searchFilters: CategoryColumns["searchFilters"] = []; const searchFilters: CategoryColumns["searchFilters"] = [];
const renderTableHeader: CategoryColumns["renderTableHeader"] = []; const renderTableHeader: CategoryColumns["renderTableHeader"] = [];
const tableRowRenderers: ((entity: CatalogEntity) => React.ReactNode)[] = []; const tableRowRenderers: ((entity: CatalogEntity) => SafeReactNode)[] = [];
for (const registration of allRegistrations) { for (const registration of allRegistrations) {
if (registration.sortCallback) { if (registration.sortCallback) {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type React from "react"; import type { SafeReactNode } from "@k8slens/utilities";
import type { CatalogEntity } from "../../../common/catalog"; import type { CatalogEntity } from "../../../common/catalog";
import type { TableCellProps } from "../table"; import type { TableCellProps } from "../table";
@ -32,7 +32,7 @@ export interface CategoryColumnRegistration {
/** /**
* This function will be called to generate the cells (on demand) for the column * This function will be called to generate the cells (on demand) for the column
*/ */
renderCell: (entity: CatalogEntity) => React.ReactNode; renderCell: (entity: CatalogEntity) => SafeReactNode;
/** /**
* This function will be used to generate the columns title cell. * This function will be used to generate the columns title cell.
@ -79,7 +79,7 @@ export interface AdditionalCategoryColumnRegistration extends CategoryColumnRegi
export interface RegisteredAdditionalCategoryColumn { export interface RegisteredAdditionalCategoryColumn {
id: string; id: string;
priority: number; priority: number;
renderCell: (entity: CatalogEntity) => React.ReactNode; renderCell: (entity: CatalogEntity) => SafeReactNode;
titleProps: TableCellProps; titleProps: TableCellProps;
sortCallback?: (entity: CatalogEntity) => string | number | (string | number)[]; sortCallback?: (entity: CatalogEntity) => string | number | (string | number)[];
searchFilter?: (entity: CatalogEntity) => string | string[]; searchFilter?: (entity: CatalogEntity) => string | string[];

View File

@ -2,7 +2,6 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { ReactNode } from "react";
import React, { useState } from "react"; import React, { useState } from "react";
import { MenuItem } from "../menu"; import { MenuItem } from "../menu";
@ -12,6 +11,7 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar"; import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable"; import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable";
import type { SafeReactNode } from "@k8slens/utilities";
interface Dependencies { interface Dependencies {
activeHotbar: IComputedValue<Hotbar | undefined>; activeHotbar: IComputedValue<Hotbar | undefined>;
@ -19,8 +19,8 @@ interface Dependencies {
interface HotbarToggleMenuItemProps { interface HotbarToggleMenuItemProps {
entity: CatalogEntity; entity: CatalogEntity;
addContent: ReactNode; addContent: SafeReactNode;
removeContent: ReactNode; removeContent: SafeReactNode;
} }
function NonInjectedHotbarToggleMenuItem({ function NonInjectedHotbarToggleMenuItem({

View File

@ -5,17 +5,17 @@
import "./checkbox.scss"; import "./checkbox.scss";
import React from "react"; import React from "react";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames, noop } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities";
export interface CheckboxProps { export interface CheckboxProps {
className?: string; className?: string;
label?: React.ReactNode; label?: SafeReactNode;
inline?: boolean; inline?: boolean;
disabled?: boolean; disabled?: boolean;
value?: boolean; value?: boolean;
onChange?(value: boolean, evt: React.ChangeEvent<HTMLInputElement>): void; onChange?(value: boolean, evt: React.ChangeEvent<HTMLInputElement>): void;
children?: SingleOrMany<React.ReactChild | React.ReactFragment>; children?: SingleOrMany<SafeReactNode>;
} }
export function Checkbox({ label, inline, className, value, children, onChange = noop, disabled, ...inputProps }: CheckboxProps) { export function Checkbox({ label, inline, className, value, children, onChange = noop, disabled, ...inputProps }: CheckboxProps) {

View File

@ -22,6 +22,7 @@ import type { RequestClusterActivation } from "../../../features/cluster/activat
import requestClusterActivationInjectable from "../../../features/cluster/activation/renderer/request-activation.injectable"; import requestClusterActivationInjectable from "../../../features/cluster/activation/renderer/request-activation.injectable";
import type { GetClusterById } from "../../../features/cluster/storage/common/get-by-id.injectable"; import type { GetClusterById } from "../../../features/cluster/storage/common/get-by-id.injectable";
import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable"; import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable";
import type { SafeReactNode } from "@k8slens/utilities";
interface Dependencies { interface Dependencies {
clusterId: IComputedValue<string>; clusterId: IComputedValue<string>;
@ -93,7 +94,7 @@ class NonInjectedClusterView extends React.Component<Dependencies> {
]); ]);
} }
renderStatus(): React.ReactNode { renderStatus(): SafeReactNode {
const { cluster, isReady } = this; const { cluster, isReady } = this;
if (cluster && !isReady) { if (cluster && !isReady) {

View File

@ -14,9 +14,10 @@ import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-create
import type { DiRender } from "../test-utils/renderFor"; import type { DiRender } from "../test-utils/renderFor";
import { renderFor } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor";
import { HorizontalPodAutoscalerDetails } from "./details"; import { HorizontalPodAutoscalerDetails } from "./details";
import type { SafeReactNode } from "@k8slens/utilities";
jest.mock("react-router-dom", () => ({ jest.mock("react-router-dom", () => ({
Link: ({ children }: { children: React.ReactNode }) => children, Link: ({ children }: { children: SafeReactNode }) => children,
})); }));
const hpaV2 = { const hpaV2 = {

View File

@ -5,11 +5,11 @@
import "./confirm-dialog.scss"; import "./confirm-dialog.scss";
import type { ReactNode } from "react";
import React from "react"; import React from "react";
import type { IObservableValue } from "mobx"; import type { IObservableValue } from "mobx";
import { observable, makeObservable, computed } from "mobx"; import { observable, makeObservable, computed } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames, noop, prevDefault } from "@k8slens/utilities"; import { cssNames, noop, prevDefault } from "@k8slens/utilities";
import type { ButtonProps } from "@k8slens/button"; import type { ButtonProps } from "@k8slens/button";
import { Button } from "@k8slens/button"; import { Button } from "@k8slens/button";
@ -30,10 +30,10 @@ export interface ConfirmDialogParams extends ConfirmDialogBooleanParams {
} }
export interface ConfirmDialogBooleanParams { export interface ConfirmDialogBooleanParams {
labelOk?: ReactNode; labelOk?: SafeReactNode;
labelCancel?: ReactNode; labelCancel?: SafeReactNode;
message: ReactNode; message: SafeReactNode;
icon?: ReactNode; icon?: SafeReactNode;
okButtonProps?: Partial<ButtonProps>; okButtonProps?: Partial<ButtonProps>;
cancelButtonProps?: Partial<ButtonProps>; cancelButtonProps?: Partial<ButtonProps>;
} }

View File

@ -7,6 +7,7 @@ import "./crd-resource-details.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames, safeJSONPathValue } from "@k8slens/utilities"; import { cssNames, safeJSONPathValue } from "@k8slens/utilities";
import { Badge } from "../badge"; import { Badge } from "../badge";
import { DrawerItem } from "../drawer"; import { DrawerItem } from "../drawer";
@ -22,7 +23,7 @@ export interface CustomResourceDetailsProps extends KubeObjectDetailsProps<KubeO
crd?: CustomResourceDefinition; crd?: CustomResourceDefinition;
} }
function convertSpecValue(value: unknown): React.ReactNode { function convertSpecValue(value: unknown): SafeReactNode {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return ( return (
<ul> <ul>

View File

@ -106,7 +106,11 @@ class NonInjectedCustomResources extends React.Component<Dependencies> {
isNamespaced && ( isNamespaced && (
<NamespaceSelectBadge namespace={customResource.getNs() as string} /> <NamespaceSelectBadge namespace={customResource.getNs() as string} />
), ),
...extraColumns.map((column) => safeJSONPathValue(customResource, column.jsonPath)), ...(
extraColumns
.map((column) => safeJSONPathValue(customResource, column.jsonPath))
.map(formatJSONValue)
),
<KubeObjectAge key="age" object={customResource} />, <KubeObjectAge key="age" object={customResource} />,
]} ]}
failedToLoadMessage={( failedToLoadMessage={(

View File

@ -10,6 +10,7 @@ import { createPortal } from "react-dom";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import { reaction } from "mobx"; import { reaction } from "mobx";
import { Animate } from "../animate"; import { Animate } from "../animate";
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames, noop, stopPropagation } from "@k8slens/utilities"; import { cssNames, noop, stopPropagation } from "@k8slens/utilities";
import type { ObservableHistory } from "mobx-observable-history"; import type { ObservableHistory } from "mobx-observable-history";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
@ -29,7 +30,7 @@ export interface DialogProps {
pinned?: boolean; pinned?: boolean;
animated?: boolean; animated?: boolean;
"data-testid"?: string; "data-testid"?: string;
children?: React.ReactNode | React.ReactNode[]; children?: SingleOrMany<SafeReactNode>;
} }
interface DialogState { interface DialogState {

View File

@ -7,6 +7,7 @@ import styles from "./dock-tab.module.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames, prevDefault, isMiddleClick } from "@k8slens/utilities"; import { cssNames, prevDefault, isMiddleClick } from "@k8slens/utilities";
import type { DockStore, DockTab as DockTabModel } from "./dock/store"; import type { DockStore, DockTab as DockTabModel } from "./dock/store";
import type { TabProps } from "../tabs"; import type { TabProps } from "../tabs";
@ -21,7 +22,7 @@ import isMacInjectable from "../../../common/vars/is-mac.injectable";
import autoBindReact from "auto-bind/react"; import autoBindReact from "auto-bind/react";
export interface DockTabProps extends TabProps<DockTabModel> { export interface DockTabProps extends TabProps<DockTabModel> {
moreActions?: React.ReactNode; moreActions?: SafeReactNode;
} }
interface Dependencies { interface Dependencies {

View File

@ -5,10 +5,10 @@
import "./info-panel.scss"; import "./info-panel.scss";
import type { ReactNode } from "react";
import React, { Component } from "react"; import React, { Component } from "react";
import { computed, observable, reaction, makeObservable } from "mobx"; import { computed, observable, reaction, makeObservable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { Button } from "@k8slens/button"; import { Button } from "@k8slens/button";
import { Icon } from "../icon"; import { Icon } from "../icon";
@ -29,9 +29,9 @@ export interface InfoPanelProps extends OptionalProps {
export interface OptionalProps { export interface OptionalProps {
className?: string; className?: string;
error?: string; error?: string;
controls?: ReactNode; controls?: SafeReactNode;
submitLabel?: ReactNode; submitLabel?: SafeReactNode;
submittingMessage?: ReactNode; submittingMessage?: SafeReactNode;
disableSubmit?: boolean; disableSubmit?: boolean;
showButtons?: boolean; showButtons?: boolean;
showSubmitClose?: boolean; showSubmitClose?: boolean;

View File

@ -5,10 +5,11 @@
import "./drawer-item.scss"; import "./drawer-item.scss";
import React from "react"; import React from "react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
export interface DrawerItemProps extends React.HTMLAttributes<HTMLDivElement> { export interface DrawerItemProps extends React.HTMLAttributes<HTMLDivElement> {
name: React.ReactNode; name: SafeReactNode;
title?: string; title?: string;
labelsOnly?: boolean; labelsOnly?: boolean;
hidden?: boolean; hidden?: boolean;

View File

@ -6,11 +6,12 @@
import "./drawer-param-toggler.scss"; import "./drawer-param-toggler.scss";
import React from "react"; import React from "react";
import { Icon } from "../icon"; import { Icon } from "../icon";
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
export interface DrawerParamTogglerProps { export interface DrawerParamTogglerProps {
label: string | number; label: string | number;
children: React.ReactNode | React.ReactNode[]; children: SingleOrMany<SafeReactNode>;
} }
interface State { interface State {

View File

@ -5,16 +5,17 @@
import styles from "./drawer-title.module.css"; import styles from "./drawer-title.module.css";
import React from "react"; import React from "react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
export interface DrawerTitleProps { export interface DrawerTitleProps {
className?: string; className?: string;
children?: React.ReactNode; children?: SafeReactNode;
/** /**
* @deprecated Prefer passing the value as `children` * @deprecated Prefer passing the value as `children`
*/ */
title?: React.ReactNode; title?: SafeReactNode;
/** /**
* Specifies how large this title is * Specifies how large this title is

View File

@ -8,7 +8,7 @@ import "./drawer.scss";
import React from "react"; import React from "react";
import { clipboard } from "electron"; import { clipboard } from "electron";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames, noop } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities";
import { Icon } from "../icon"; import { Icon } from "../icon";
import type { AnimateName } from "../animate"; import type { AnimateName } from "../animate";
@ -24,7 +24,7 @@ export type DrawerPosition = "top" | "left" | "right" | "bottom";
export interface DrawerProps { export interface DrawerProps {
open: boolean; open: boolean;
title: React.ReactNode; title: SafeReactNode;
/** /**
* The width or heigh (depending on `position`) of the Drawer. * The width or heigh (depending on `position`) of the Drawer.
@ -38,8 +38,8 @@ export interface DrawerProps {
position?: DrawerPosition; position?: DrawerPosition;
animation?: AnimateName; animation?: AnimateName;
onClose?: () => void; onClose?: () => void;
toolbar?: React.ReactNode; toolbar?: SafeReactNode;
children?: SingleOrMany<React.ReactNode>; children?: SingleOrMany<SafeReactNode>;
"data-testid"?: string; "data-testid"?: string;
testIdForClose?: string; testIdForClose?: string;
} }

View File

@ -3,12 +3,15 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { toSafeReactChildrenArray } from "@k8slens/utilities";
import type { HTMLAttributes } from "react"; import type { HTMLAttributes } from "react";
import React, { useState } from "react"; import React, { useState } from "react";
import { Menu } from "../menu"; import { Menu } from "../menu";
interface DropdownProps extends HTMLAttributes<HTMLDivElement> { interface DropdownProps extends HTMLAttributes<HTMLDivElement> {
contentForToggle: React.ReactNode; contentForToggle: SafeReactNode;
children?: SingleOrMany<SafeReactNode>;
} }
export function Dropdown(props: DropdownProps) { export function Dropdown(props: DropdownProps) {
@ -31,7 +34,7 @@ export function Dropdown(props: DropdownProps) {
close={toggle} close={toggle}
open={toggle} open={toggle}
> >
{React.Children.toArray(children)} {toSafeReactChildrenArray(children)}
</Menu> </Menu>
</div> </div>
); );

View File

@ -22,7 +22,7 @@ const everySecond = 1000;
const everyMinute = 60 * 1000; const everyMinute = 60 * 1000;
/** /**
* This function computes a resonable update interval, matching `formatDuration`'s rules on when to display seconds * This function computes a reasonable update interval, matching `formatDuration`'s rules on when to display seconds
*/ */
function computeUpdateInterval(creationTimestampEpoch: number, compact: boolean): number { function computeUpdateInterval(creationTimestampEpoch: number, compact: boolean): number {
const seconds = Math.floor((Date.now() - creationTimestampEpoch) / 1000); const seconds = Math.floor((Date.now() - creationTimestampEpoch) / 1000);

View File

@ -11,7 +11,7 @@ import React from "react";
import { Icon } from "../icon"; import { Icon } from "../icon";
import type { InputProps, InputValidator } from "../input"; import type { InputProps, InputValidator } from "../input";
import { Input } from "../input"; import { Input } from "../input";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import autoBindReact from "auto-bind/react"; import autoBindReact from "auto-bind/react";
export interface EditableListProps<T> { export interface EditableListProps<T> {
@ -23,7 +23,7 @@ export interface EditableListProps<T> {
// An optional prop used to convert T to a displayable string // An optional prop used to convert T to a displayable string
// defaults to `String` // defaults to `String`
renderItem?: (item: T, index: number) => React.ReactNode; renderItem?: (item: T, index: number) => SafeReactNode;
inputTheme?: InputProps["theme"]; inputTheme?: InputProps["theme"];
} }

View File

@ -13,6 +13,7 @@ import { Spinner } from "../spinner";
import { observable, makeObservable } from "mobx"; import { observable, makeObservable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import _ from "lodash"; import _ from "lodash";
import type { SafeReactNode } from "@k8slens/utilities";
export interface FileUploadProps { export interface FileUploadProps {
uploadDir: string; uploadDir: string;
@ -48,7 +49,7 @@ export enum OverTotalSizeLimitStyle {
export interface BaseProps { export interface BaseProps {
accept?: string; accept?: string;
label: React.ReactNode; label: SafeReactNode;
multiple?: boolean; multiple?: boolean;
// limit is the optional maximum number of files to upload // limit is the optional maximum number of files to upload
@ -220,7 +221,7 @@ class DefaultedFilePicker extends React.Component<FilePickerProps & typeof defau
); );
} }
getIconRight(): React.ReactNode { getIconRight(): SafeReactNode {
switch (this.status) { switch (this.status) {
case FileInputStatus.PROCESSING: case FileInputStatus.PROCESSING:
return <Spinner />; return <Spinner />;

View File

@ -4,13 +4,12 @@
*/ */
import "./hotbar-menu.scss"; import "./hotbar-menu.scss";
import type { HTMLAttributes, ReactNode } from "react";
import React, { useState } from "react"; import React, { useState } from "react";
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
export interface HotbarCellProps extends HTMLAttributes<HTMLDivElement> { export interface HotbarCellProps extends React.HTMLAttributes<HTMLDivElement> {
children?: ReactNode; children?: SingleOrMany<SafeReactNode>;
index: number; index: number;
innerRef?: React.Ref<HTMLDivElement>; innerRef?: React.Ref<HTMLDivElement>;
} }

View File

@ -8,7 +8,7 @@ import "./hotbar-menu.scss";
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { HotbarEntityIcon } from "./hotbar-entity-icon"; import { HotbarEntityIcon } from "./hotbar-entity-icon";
import type { IClassName } from "@k8slens/utilities"; import type { IClassName, SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import type { CatalogEntity } from "../../api/catalog-entity"; import type { CatalogEntity } from "../../api/catalog-entity";
@ -147,7 +147,7 @@ const NonInjectedHotbarMenu = observer((props: Dependencies & HotbarMenuProps) =
)} )}
</Draggable> </Draggable>
)} )}
{provided.placeholder} {provided.placeholder as SafeReactNode}
</HotbarCell> </HotbarCell>
)} )}
</Droppable> </Droppable>

View File

@ -88,7 +88,7 @@ const NonInjectedHotbarSelector = observer(({
targetId="hotbarIndex" targetId="hotbarIndex"
preferredPositions={[TooltipPosition.TOP, TooltipPosition.TOP_LEFT]} preferredPositions={[TooltipPosition.TOP, TooltipPosition.TOP_LEFT]}
> >
{hotbar?.name} {hotbar?.name.get()}
</Tooltip> </Tooltip>
</div> </div>
<Icon <Icon

View File

@ -5,7 +5,6 @@
import "./icon.scss"; import "./icon.scss";
import type { ReactNode } from "react";
import React, { createRef } from "react"; import React, { createRef } from "react";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import type { LocationDescriptor } from "history"; import type { LocationDescriptor } from "history";
@ -37,6 +36,7 @@ import Workloads from "./workloads.svg";
import type { Logger } from "@k8slens/logger"; import type { Logger } from "@k8slens/logger";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import { loggerInjectionToken } from "@k8slens/logger"; import { loggerInjectionToken } from "@k8slens/logger";
import type { SingleOrMany, SafeReactNode } from "@k8slens/utilities";
const hrefValidation = /https?:\/\//; const hrefValidation = /https?:\/\//;
@ -158,7 +158,9 @@ export interface BaseIconProps {
"data-testid"?: string; "data-testid"?: string;
} }
export interface IconProps extends React.HTMLAttributes<any>, BaseIconProps {} export interface IconProps extends React.HTMLAttributes<any>, BaseIconProps {
children?: SingleOrMany<SafeReactNode>;
}
export function isSvg(content: string): boolean { export function isSvg(content: string): boolean {
// source code of the asset // source code of the asset
@ -204,7 +206,7 @@ const RawIcon = (props: IconProps & Dependencies) => {
onKeyDown?.(event); onKeyDown?.(event);
}; };
let iconContent: ReactNode; let iconContent: SafeReactNode;
const iconProps: Partial<IconProps> = { const iconProps: Partial<IconProps> = {
className: cssNames("Icon", className, className: cssNames("Icon", className,
{ svg, material, interactive: isInteractive, disabled, sticker, active, focusable }, { svg, material, interactive: isInteractive, disabled, sticker, active, focusable },

View File

@ -7,7 +7,7 @@ import "./input.scss";
import type { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react"; import type { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react";
import React from "react"; import React from "react";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { debouncePromise, isPromiseSettledFulfilled, cssNames } from "@k8slens/utilities"; import { debouncePromise, isPromiseSettledFulfilled, cssNames } from "@k8slens/utilities";
import { Icon } from "../icon"; import { Icon } from "../icon";
import type { TooltipProps } from "@k8slens/tooltip"; import type { TooltipProps } from "@k8slens/tooltip";
@ -53,12 +53,12 @@ export interface IconDataFnArg {
} }
/** /**
* One of the folloing: * One of the following:
* - A material icon name * - A material icon name
* - A react node * - A react node
* - Or a function that produces a react node * - Or a function that produces a react node
*/ */
export type IconData = string | React.ReactNode | ((opt: IconDataFnArg) => React.ReactNode); export type IconData = string | SafeReactNode | ((opt: IconDataFnArg) => SafeReactNode);
export type InputProps = Omit<InputElementProps, "onChange" | "onSubmit"> & { export type InputProps = Omit<InputElementProps, "onChange" | "onSubmit"> & {
theme?: "round-black" | "round"; theme?: "round-black" | "round";
@ -74,7 +74,7 @@ export type InputProps = Omit<InputElementProps, "onChange" | "onSubmit"> & {
showErrorsAsTooltip?: boolean | Omit<TooltipProps, "targetId">; // show validation errors as a tooltip :hover (instead of block below) showErrorsAsTooltip?: boolean | Omit<TooltipProps, "targetId">; // show validation errors as a tooltip :hover (instead of block below)
iconLeft?: IconData; iconLeft?: IconData;
iconRight?: IconData; iconRight?: IconData;
contentRight?: string | React.ReactNode; // Any component of string goes after iconRight contentRight?: string | SafeReactNode; // Any component of string goes after iconRight
validators?: SingleOrMany<InputValidator>; validators?: SingleOrMany<InputValidator>;
blurOnEnter?: boolean; blurOnEnter?: boolean;
onChange?(value: string, evt: React.ChangeEvent<InputElement>): void; onChange?(value: string, evt: React.ChangeEvent<InputElement>): void;
@ -86,7 +86,7 @@ interface State {
dirty: boolean; dirty: boolean;
valid: boolean; valid: boolean;
validating: boolean; validating: boolean;
errors: React.ReactNode[]; errors: SafeReactNode[];
submitted: boolean; submitted: boolean;
} }
@ -171,8 +171,8 @@ export class Input extends React.Component<InputProps, State> {
async validate() { async validate() {
const value = this.getValue(); const value = this.getValue();
let validationId = (this.validationId = ""); // reset every time for async validators let validationId = (this.validationId = ""); // reset every time for async validators
const asyncValidators: Promise<React.ReactNode>[] = []; const asyncValidators: Promise<SafeReactNode>[] = [];
const errors: React.ReactNode[] = []; const errors: SafeReactNode[] = [];
// run validators // run validators
for (const validator of this.validators) { for (const validator of this.validators) {
@ -192,6 +192,8 @@ export class Input extends React.Component<InputProps, State> {
asyncValidators.push((async () => { asyncValidators.push((async () => {
try { try {
await validator.validate(value, this.props); await validator.validate(value, this.props);
return undefined;
} catch (error) { } catch (error) {
return this.getValidatorError(value, validator) || (error instanceof Error ? error.message : String(error)); return this.getValidatorError(value, validator) || (error instanceof Error ? error.message : String(error));
} }
@ -220,7 +222,7 @@ export class Input extends React.Component<InputProps, State> {
this.input?.setCustomValidity(errors[0]?.toString() ?? ""); this.input?.setCustomValidity(errors[0]?.toString() ?? "");
} }
setValidation(errors: React.ReactNode[]) { setValidation(errors: SafeReactNode[]) {
this.setState({ this.setState({
validating: false, validating: false,
valid: !errors.length, valid: !errors.length,
@ -432,7 +434,7 @@ export class Input extends React.Component<InputProps, State> {
const componentId = id || showErrorsAsTooltip const componentId = id || showErrorsAsTooltip
? `input_tooltip_id_${uuid.v4()}` ? `input_tooltip_id_${uuid.v4()}`
: undefined; : undefined;
let tooltipError: React.ReactNode; let tooltipError: SafeReactNode;
if (showErrorsAsTooltip && showErrors) { if (showErrorsAsTooltip && showErrors) {
const tooltipProps = typeof showErrorsAsTooltip === "object" ? showErrorsAsTooltip : {}; const tooltipProps = typeof showErrorsAsTooltip === "object" ? showErrorsAsTooltip : {};

View File

@ -4,10 +4,10 @@
*/ */
import type { InputProps } from "./input"; import type { InputProps } from "./input";
import type React from "react";
import fse from "fs-extra"; import fse from "fs-extra";
import { TypedRegEx } from "typed-regex"; import { TypedRegEx } from "typed-regex";
import type { SetRequired } from "type-fest"; import type { SetRequired } from "type-fest";
import type { SafeReactNode } from "@k8slens/utilities";
export type InputValidationResult<IsAsync extends boolean> = export type InputValidationResult<IsAsync extends boolean> =
IsAsync extends true IsAsync extends true
@ -16,7 +16,7 @@ export type InputValidationResult<IsAsync extends boolean> =
export type InputValidation<IsAsync extends boolean> = (value: string, props?: InputProps) => InputValidationResult<IsAsync>; export type InputValidation<IsAsync extends boolean> = (value: string, props?: InputProps) => InputValidationResult<IsAsync>;
export type SyncValidationMessage = React.ReactNode | ((value: string, props?: InputProps) => React.ReactNode); export type SyncValidationMessage = SafeReactNode | ((value: string, props?: InputProps) => SafeReactNode);
/** /**
* @deprecated This type is not as type safe as it is possible to specify an async input validator without specifying a `debounce` time. * @deprecated This type is not as type safe as it is possible to specify an async input validator without specifying a `debounce` time.

View File

@ -5,7 +5,6 @@
import "./item-list-layout.scss"; import "./item-list-layout.scss";
import type { ReactNode } from "react";
import React from "react"; import React from "react";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import { computed, makeObservable } from "mobx"; import { computed, makeObservable } from "mobx";
@ -13,7 +12,7 @@ import { Observer, observer } from "mobx-react";
import type { ConfirmDialogParams } from "../confirm-dialog"; import type { ConfirmDialogParams } from "../confirm-dialog";
import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table"; import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table";
import { Table, TableCell, TableHead, TableRow } from "../table"; import { Table, TableCell, TableHead, TableRow } from "../table";
import type { IClassName } from "@k8slens/utilities"; import type { IClassName, SafeReactNode } from "@k8slens/utilities";
import { cssNames, isDefined, isReactNode, noop, prevDefault, stopPropagation } from "@k8slens/utilities"; import { cssNames, isDefined, isReactNode, noop, prevDefault, stopPropagation } from "@k8slens/utilities";
import type { AddRemoveButtonsProps } from "../add-remove-buttons"; import type { AddRemoveButtonsProps } from "../add-remove-buttons";
import { AddRemoveButtons } from "../add-remove-buttons"; import { AddRemoveButtons } from "../add-remove-buttons";
@ -50,8 +49,8 @@ export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStor
sortingCallbacks?: TableSortCallbacks<Item>; sortingCallbacks?: TableSortCallbacks<Item>;
tableProps?: Partial<TableProps<Item>>; // low-level table configuration tableProps?: Partial<TableProps<Item>>; // low-level table configuration
renderTableHeader?: (TableCellProps | undefined | null)[]; renderTableHeader?: (TableCellProps | undefined | null)[];
renderTableContents: (item: Item) => (ReactNode | TableCellProps)[]; renderTableContents: (item: Item) => (SafeReactNode | TableCellProps)[];
renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => ReactNode; renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => SafeReactNode;
customizeTableRowProps?: (item: Item) => Partial<TableRowProps<Item>>; customizeTableRowProps?: (item: Item) => Partial<TableRowProps<Item>>;
addRemoveButtons?: Partial<AddRemoveButtonsProps>; addRemoveButtons?: Partial<AddRemoveButtonsProps>;
virtual?: boolean; virtual?: boolean;
@ -71,7 +70,7 @@ export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStor
* *
* @default "Failed to load items" * @default "Failed to load items"
*/ */
failedToLoadMessage?: React.ReactNode; failedToLoadMessage?: SafeReactNode;
} }
interface Dependencies { interface Dependencies {

View File

@ -5,10 +5,9 @@
import "./item-list-layout.scss"; import "./item-list-layout.scss";
import type { ReactNode } from "react";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { IClassName } from "@k8slens/utilities"; import type { IClassName, SafeReactNode } from "@k8slens/utilities";
import { cssNames, isDefined } from "@k8slens/utilities"; import { cssNames, isDefined } from "@k8slens/utilities";
import type { ItemObject } from "@k8slens/list-layout"; import type { ItemObject } from "@k8slens/list-layout";
import type { Filter } from "./page-filters/store"; import type { Filter } from "./page-filters/store";
@ -27,8 +26,8 @@ export interface ItemListLayoutHeaderProps<I extends ItemObject, PreLoadStores e
showHeader?: boolean; showHeader?: boolean;
headerClassName?: IClassName; headerClassName?: IClassName;
renderHeaderTitle?: renderHeaderTitle?:
| ReactNode | SafeReactNode
| (() => ReactNode); | (() => SafeReactNode);
customizeHeader?: HeaderCustomizer | HeaderCustomizer[]; customizeHeader?: HeaderCustomizer | HeaderCustomizer[];
} }

View File

@ -5,13 +5,12 @@
import "./item-list-layout.scss"; import "./item-list-layout.scss";
import type { ReactNode } from "react";
import React from "react"; import React from "react";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import { computed, makeObservable, untracked } from "mobx"; import { computed, makeObservable, untracked } from "mobx";
import type { ConfirmDialogParams } from "../confirm-dialog"; import type { ConfirmDialogParams } from "../confirm-dialog";
import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table"; import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table";
import type { IClassName, SingleOrMany } from "@k8slens/utilities"; import type { IClassName, SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames, noop } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities";
import type { AddRemoveButtonsProps } from "../add-remove-buttons"; import type { AddRemoveButtonsProps } from "../add-remove-buttons";
import type { ItemObject } from "@k8slens/list-layout"; import type { ItemObject } from "@k8slens/list-layout";
@ -39,10 +38,10 @@ export type ItemsFilter<I extends ItemObject> = (items: I[]) => I[];
export type ItemsFilters<I extends ItemObject> = Record<string, ItemsFilter<I>>; export type ItemsFilters<I extends ItemObject> = Record<string, ItemsFilter<I>>;
export interface HeaderPlaceholders { export interface HeaderPlaceholders {
title?: ReactNode; title?: SafeReactNode;
searchProps?: SearchInputUrlProps; searchProps?: SearchInputUrlProps;
filters?: ReactNode; filters?: SafeReactNode;
info?: ReactNode; info?: SafeReactNode;
} }
function normalizeText(value: Primitive) { function normalizeText(value: Primitive) {
@ -79,7 +78,7 @@ export type ItemListStore<I extends ItemObject, PreLoadStores extends boolean> =
export type RenderHeaderTitle< export type RenderHeaderTitle<
Item extends ItemObject, Item extends ItemObject,
PreLoadStores extends boolean, PreLoadStores extends boolean,
> = ReactNode | ((parent: NonInjectedItemListLayout<Item, PreLoadStores>) => ReactNode); > = SafeReactNode | ((parent: NonInjectedItemListLayout<Item, PreLoadStores>) => SafeReactNode);
export type HeaderCustomizer = (placeholders: HeaderPlaceholders) => HeaderPlaceholders; export type HeaderCustomizer = (placeholders: HeaderPlaceholders) => HeaderPlaceholders;
export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends boolean = boolean> = { export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends boolean = boolean> = {
@ -108,8 +107,8 @@ export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends b
sortingCallbacks?: TableSortCallbacks<Item>; sortingCallbacks?: TableSortCallbacks<Item>;
tableProps?: Partial<TableProps<Item>>; // low-level table configuration tableProps?: Partial<TableProps<Item>>; // low-level table configuration
renderTableHeader?: (TableCellProps | undefined | null)[]; renderTableHeader?: (TableCellProps | undefined | null)[];
renderTableContents: (item: Item) => (ReactNode | TableCellProps)[]; renderTableContents: (item: Item) => (SafeReactNode | TableCellProps)[];
renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => ReactNode; renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => SafeReactNode;
customizeTableRowProps?: (item: Item) => Partial<TableRowProps<Item>>; customizeTableRowProps?: (item: Item) => Partial<TableRowProps<Item>>;
addRemoveButtons?: Partial<AddRemoveButtonsProps>; addRemoveButtons?: Partial<AddRemoveButtonsProps>;
virtual?: boolean; virtual?: boolean;
@ -121,7 +120,7 @@ export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends b
// other // other
customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>; customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>;
renderFooter?: (parent: NonInjectedItemListLayout<Item, PreLoadStores>) => React.ReactNode; renderFooter?: (parent: NonInjectedItemListLayout<Item, PreLoadStores>) => SafeReactNode;
spinnerTestId?: string; spinnerTestId?: string;
@ -130,7 +129,7 @@ export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends b
* *
* @default "Failed to load items" * @default "Failed to load items"
*/ */
failedToLoadMessage?: React.ReactNode; failedToLoadMessage?: SafeReactNode;
filterCallbacks?: ItemsFilters<Item>; filterCallbacks?: ItemsFilters<Item>;
"data-testid"?: string; "data-testid"?: string;

View File

@ -7,6 +7,7 @@ import styles from "./kubeconfig-dialog.module.scss";
import React from "react"; import React from "react";
import type { IObservableValue } from "mobx"; import type { IObservableValue } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { Button } from "@k8slens/button"; import { Button } from "@k8slens/button";
import type { DialogProps } from "../dialog"; import type { DialogProps } from "../dialog";
@ -22,7 +23,7 @@ import kubeconfigDialogStateInjectable from "./state.injectable";
import { saveFileDialog } from "../../utils/saveFile"; import { saveFileDialog } from "../../utils/saveFile";
export interface KubeconfigDialogData { export interface KubeconfigDialogData {
title?: React.ReactNode; title?: SafeReactNode;
config: string; config: string;
} }

View File

@ -3,13 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type React from "react";
import { loggerInjectionToken } from "@k8slens/logger"; import { loggerInjectionToken } from "@k8slens/logger";
import showCheckedErrorNotificationInjectable from "../notifications/show-checked-error.injectable"; import showCheckedErrorNotificationInjectable from "../notifications/show-checked-error.injectable";
import kubeconfigDialogStateInjectable from "./state.injectable"; import kubeconfigDialogStateInjectable from "./state.injectable";
import type { SafeReactNode } from "@k8slens/utilities";
export interface OpenKubeconfigDialogArgs { export interface OpenKubeconfigDialogArgs {
title?: React.ReactNode; title?: SafeReactNode;
loader: () => Promise<string>; loader: () => Promise<string>;
} }
@ -29,8 +29,8 @@ const openKubeconfigDialogInjectable = getInjectable({
state.set({ title, config }); state.set({ title, config });
} catch (error) { } catch (error) {
showCheckedErrorNotification(error, "Failed to retrive config for dialog"); showCheckedErrorNotification(error, "Failed to retrieve config for dialog");
logger.warn("[KUBEOCONFIG-DIALOG]: failed to retrived config for dialog", error); logger.warn("[KUBECONFIG-DIALOG]: failed to retrieve config for dialog", error);
} }
})(); })();
}; };

View File

@ -7,12 +7,13 @@ import type { IconProps } from "../icon";
import type React from "react"; import type React from "react";
import type { PageTarget } from "../../routes/page-registration"; import type { PageTarget } from "../../routes/page-registration";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import type { SafeReactNode } from "@k8slens/utilities";
export interface ClusterPageMenuRegistration { export interface ClusterPageMenuRegistration {
id?: string; id?: string;
parentId?: string; parentId?: string;
target?: PageTarget; target?: PageTarget;
title: React.ReactNode; title: SafeReactNode;
components: ClusterPageMenuComponents; components: ClusterPageMenuComponents;
visible?: IComputedValue<boolean>; visible?: IComputedValue<boolean>;
orderNumber?: number; orderNumber?: number;

View File

@ -7,6 +7,7 @@ import styles from "./main-layout.module.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { ErrorBoundary } from "@k8slens/error-boundary"; import { ErrorBoundary } from "@k8slens/error-boundary";
import { ResizeDirection, ResizeGrowthDirection, ResizeSide, ResizingAnchor } from "@k8slens/resizing-anchor"; import { ResizeDirection, ResizeGrowthDirection, ResizeSide, ResizingAnchor } from "@k8slens/resizing-anchor";
@ -16,10 +17,10 @@ import sidebarStorageInjectable, { defaultSidebarWidth } from "./sidebar-storage
import type { StorageLayer } from "../../utils/storage-helper"; import type { StorageLayer } from "../../utils/storage-helper";
export interface MainLayoutProps { export interface MainLayoutProps {
sidebar: React.ReactNode; sidebar: SafeReactNode;
className?: string; className?: string;
footer?: React.ReactNode; footer?: SafeReactNode;
children?: React.ReactNode | React.ReactNode[]; children?: SingleOrMany<SafeReactNode>;
} }
/** /**

View File

@ -7,7 +7,7 @@ import "./setting-layout.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { IClassName } from "@k8slens/utilities"; import type { IClassName, SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { CloseButton } from "./close-button"; import { CloseButton } from "./close-button";
import { getLegacyGlobalDiForExtensionApi } from "@k8slens/legacy-global-di"; import { getLegacyGlobalDiForExtensionApi } from "@k8slens/legacy-global-di";
@ -19,7 +19,7 @@ export interface SettingLayoutProps extends React.DOMAttributes<any> {
contentClass?: IClassName; contentClass?: IClassName;
provideBackButtonNavigation?: boolean; provideBackButtonNavigation?: boolean;
contentGaps?: boolean; contentGaps?: boolean;
navigation?: React.ReactNode; navigation?: SafeReactNode;
back?: (evt: React.MouseEvent | KeyboardEvent) => void; back?: (evt: React.MouseEvent | KeyboardEvent) => void;
closeButtonProps?: { "data-testid"?: string }; closeButtonProps?: { "data-testid"?: string };
} }

View File

@ -9,9 +9,10 @@ import React from "react";
import siblingTabsInjectable from "../../routes/sibling-tabs.injectable"; import siblingTabsInjectable from "../../routes/sibling-tabs.injectable";
import { TabLayout } from "./tab-layout-2"; import { TabLayout } from "./tab-layout-2";
import type { HierarchicalSidebarItem } from "./sidebar-items.injectable"; import type { HierarchicalSidebarItem } from "./sidebar-items.injectable";
import type { SafeReactNode } from "@k8slens/utilities";
interface SiblingTabLayoutProps { interface SiblingTabLayoutProps {
children: React.ReactNode; children: SafeReactNode;
scrollable?: boolean; scrollable?: boolean;
} }

View File

@ -8,13 +8,14 @@ import { computed } from "mobx";
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx"; import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
import { byOrderNumber } from "../../../common/utils/composable-responsibilities/orderable/orderable"; import { byOrderNumber } from "../../../common/utils/composable-responsibilities/orderable/orderable";
import type { SetRequired } from "type-fest"; import type { SetRequired } from "type-fest";
import type { SafeReactNode } from "@k8slens/utilities";
export interface SidebarItemRegistration { export interface SidebarItemRegistration {
id: string; id: string;
parentId: string | null; parentId: string | null;
title: React.ReactNode; title: SafeReactNode;
onClick: () => void; onClick: () => void;
getIcon?: () => React.ReactNode; getIcon?: () => SafeReactNode;
isActive?: IComputedValue<boolean>; isActive?: IComputedValue<boolean>;
isVisible?: IComputedValue<boolean>; isVisible?: IComputedValue<boolean>;
orderNumber: number; orderNumber: number;

View File

@ -5,14 +5,14 @@
import "./sub-header.scss"; import "./sub-header.scss";
import React from "react"; import React from "react";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
export interface SubHeaderProps { export interface SubHeaderProps {
className?: string; className?: string;
withLine?: boolean; // add bottom line withLine?: boolean; // add bottom line
compact?: boolean; // no extra padding around content compact?: boolean; // no extra padding around content
children: SingleOrMany<React.ReactNode>; children: SingleOrMany<SafeReactNode>;
} }
export class SubHeader extends React.Component<SubHeaderProps> { export class SubHeader extends React.Component<SubHeaderProps> {

View File

@ -5,15 +5,15 @@
import "./sub-title.scss"; import "./sub-title.scss";
import React from "react"; import React from "react";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
export interface SubTitleProps { export interface SubTitleProps {
className?: string; className?: string;
title: React.ReactNode; title: SafeReactNode;
compact?: boolean; // no bottom padding compact?: boolean; // no bottom padding
id?: string; id?: string;
children?: SingleOrMany<React.ReactNode>; children?: SingleOrMany<SafeReactNode>;
} }
export class SubTitle extends React.Component<SubTitleProps> { export class SubTitle extends React.Component<SubTitleProps> {

View File

@ -7,6 +7,7 @@ import "./tab-layout.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { Tab, Tabs } from "../tabs"; import { Tab, Tabs } from "../tabs";
import { ErrorBoundary } from "@k8slens/error-boundary"; import { ErrorBoundary } from "@k8slens/error-boundary";
@ -14,7 +15,7 @@ import type { HierarchicalSidebarItem } from "./sidebar-items.injectable";
export interface TabLayoutProps { export interface TabLayoutProps {
tabs?: HierarchicalSidebarItem[]; tabs?: HierarchicalSidebarItem[];
children?: React.ReactNode; children?: SafeReactNode;
scrollable?: boolean; scrollable?: boolean;
} }

View File

@ -5,11 +5,10 @@
import "./tab-layout.scss"; import "./tab-layout.scss";
import type { ReactNode } from "react";
import React from "react"; import React from "react";
import { matchPath, Redirect, Route, Switch } from "react-router"; import { matchPath, Redirect, Route, Switch } from "react-router";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { IClassName } from "@k8slens/utilities"; import type { IClassName, SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { Tab, Tabs } from "../tabs"; import { Tab, Tabs } from "../tabs";
import { ErrorBoundary } from "@k8slens/error-boundary"; import { ErrorBoundary } from "@k8slens/error-boundary";
@ -23,13 +22,13 @@ export interface TabLayoutProps {
className?: IClassName; className?: IClassName;
contentClass?: IClassName; contentClass?: IClassName;
tabs?: TabLayoutRoute[]; tabs?: TabLayoutRoute[];
children?: ReactNode; children?: SafeReactNode;
scrollable?: boolean; scrollable?: boolean;
} }
export interface TabLayoutRoute { export interface TabLayoutRoute {
routePath: string; routePath: string;
title: React.ReactNode; title: SafeReactNode;
component: React.ComponentType<any>; component: React.ComponentType<any>;
url?: string; // page-url, if not provided `routePath` is used (doesn't work when path has some :placeholder(s)) url?: string; // page-url, if not provided `routePath` is used (doesn't work when path has some :placeholder(s))
exact?: boolean; // route-path matching rule exact?: boolean; // route-path matching rule

View File

@ -6,16 +6,16 @@
import "./wizard-layout.scss"; import "./wizard-layout.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { IClassName } from "@k8slens/utilities"; import type { IClassName, SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
export interface WizardLayoutProps extends React.DOMAttributes<any> { export interface WizardLayoutProps extends React.DOMAttributes<any> {
className?: IClassName; className?: IClassName;
header?: React.ReactNode; header?: SafeReactNode;
headerClass?: IClassName; headerClass?: IClassName;
contentClass?: IClassName; contentClass?: IClassName;
infoPanelClass?: IClassName; infoPanelClass?: IClassName;
infoPanel?: React.ReactNode; infoPanel?: SafeReactNode;
centered?: boolean; // Centering content horizontally centered?: boolean; // Centering content horizontally
} }

View File

@ -5,6 +5,7 @@
import "./line-progress.scss"; import "./line-progress.scss";
import React from "react"; import React from "react";
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { withTooltip } from "@k8slens/tooltip"; import { withTooltip } from "@k8slens/tooltip";
@ -14,6 +15,7 @@ export interface LineProgressProps extends React.HTMLProps<HTMLDivElement> {
max?: number; max?: number;
className?: any; className?: any;
precise?: number; precise?: number;
children?: SingleOrMany<SafeReactNode>;
} }
function valuePercent({ value, min, max, precise }: Required<Pick<LineProgressProps, "value" | "min" | "max" | "precise">>) { function valuePercent({ value, min, max, precise }: Required<Pick<LineProgressProps, "value" | "min" | "max" | "precise">>) {

View File

@ -9,13 +9,14 @@ import { SearchInput } from "../input";
import type { UseTableOptions } from "react-table"; import type { UseTableOptions } from "react-table";
import { ReactTable } from "../table/react-table"; import { ReactTable } from "../table/react-table";
import type { SafeReactNode } from "@k8slens/utilities";
export type SearchFilter<T> = (item: T) => string | number; export type SearchFilter<T> = (item: T) => string | number;
export interface ListProps<T> extends UseTableOptions<any> { export interface ListProps<T> extends UseTableOptions<any> {
items: T[]; items: T[];
filters: SearchFilter<T>[]; filters: SearchFilter<T>[];
title?: React.ReactNode; title?: SafeReactNode;
} }
export function List<T>({ columns, data, title, items, filters }: ListProps<T>) { export function List<T>({ columns, data, title, items, filters }: ListProps<T>) {

View File

@ -8,6 +8,7 @@ import "./menu-actions.scss";
import React, { isValidElement } from "react"; import React, { isValidElement } from "react";
import { observable, makeObservable, reaction } from "mobx"; import { observable, makeObservable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import type { IconProps } from "../icon"; import type { IconProps } from "../icon";
import { Icon } from "../icon"; import { Icon } from "../icon";
@ -25,11 +26,11 @@ export interface MenuActionsProps extends Partial<MenuProps> {
className?: string; className?: string;
toolbar?: boolean; // display menu as toolbar with icons toolbar?: boolean; // display menu as toolbar with icons
autoCloseOnSelect?: boolean; autoCloseOnSelect?: boolean;
triggerIcon?: string | (IconProps & TooltipDecoratorProps) | React.ReactNode; triggerIcon?: string | (IconProps & TooltipDecoratorProps) | SafeReactNode;
/** /**
* @deprecated Provide your own remove `<MenuItem>` as part of the `children` passed to this component * @deprecated Provide your own remove `<MenuItem>` as part of the `children` passed to this component
*/ */
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode); removeConfirmationMessage?: SafeReactNode | (() => SafeReactNode);
/** /**
* @deprecated Provide your own update `<MenuItem>` as part of the `children` passed to this component * @deprecated Provide your own update `<MenuItem>` as part of the `children` passed to this component
*/ */

View File

@ -5,9 +5,10 @@
import "./menu.scss"; import "./menu.scss";
import type { ReactElement, ReactNode } from "react"; import type { ReactElement } from "react";
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames, noop } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities";
import { Animate } from "../animate"; import { Animate } from "../animate";
import type { IconProps } from "../icon"; import type { IconProps } from "../icon";
@ -48,7 +49,7 @@ export interface MenuProps {
closeOnClickOutside?: boolean; // use false value for sub-menus closeOnClickOutside?: boolean; // use false value for sub-menus
closeOnScroll?: boolean; // applicable when usePortal={true} closeOnScroll?: boolean; // applicable when usePortal={true}
position?: MenuPosition; // applicable when usePortal={false} position?: MenuPosition; // applicable when usePortal={false}
children?: ReactNode; children?: SafeReactNode;
animated?: boolean; animated?: boolean;
toggleEvent?: "click" | "contextmenu"; toggleEvent?: "click" | "contextmenu";
"data-testid"?: string; "data-testid"?: string;

View File

@ -12,9 +12,10 @@ import { renderFor } from "../test-utils/renderFor";
import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable"; import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable";
import { NamespaceTreeView } from "./namespace-tree-view"; import { NamespaceTreeView } from "./namespace-tree-view";
import type { NamespaceTree } from "./store"; import type { NamespaceTree } from "./store";
import type { SafeReactNode } from "@k8slens/utilities";
jest.mock("react-router-dom", () => ({ jest.mock("react-router-dom", () => ({
Link: ({ children }: { children: React.ReactNode }) => children, Link: ({ children }: { children: SafeReactNode }) => children,
})); }));
function createNamespace(name: string, labels?: Record<string, string>, annotations?: Record<string, string>): Namespace { function createNamespace(name: string, labels?: Record<string, string>, annotations?: Record<string, string>): Namespace {

View File

@ -6,12 +6,12 @@
import "./no-items.scss"; import "./no-items.scss";
import React from "react"; import React from "react";
import type { IClassName } from "@k8slens/utilities"; import type { IClassName, SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
export interface NoItemsProps { export interface NoItemsProps {
className?: IClassName; className?: IClassName;
children?: React.ReactNode; children?: SafeReactNode;
} }
export function NoItems(props: NoItemsProps) { export function NoItems(props: NoItemsProps) {

View File

@ -15,6 +15,7 @@ import { Table, TableCell, TableHead, TableRow } from "../table";
import type { Logger } from "@k8slens/logger"; import type { Logger } from "@k8slens/logger";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import { loggerInjectionToken } from "@k8slens/logger"; import { loggerInjectionToken } from "@k8slens/logger";
import type { SafeReactNode } from "@k8slens/utilities";
export interface PodSecurityPolicyDetailsProps extends KubeObjectDetailsProps<PodSecurityPolicy> { export interface PodSecurityPolicyDetailsProps extends KubeObjectDetailsProps<PodSecurityPolicy> {
} }
@ -33,7 +34,7 @@ interface Dependencies {
@observer @observer
class NonInjectedPodSecurityPolicyDetails extends React.Component<PodSecurityPolicyDetailsProps & Dependencies> { class NonInjectedPodSecurityPolicyDetails extends React.Component<PodSecurityPolicyDetailsProps & Dependencies> {
renderRuleGroup(title: React.ReactNode, group: RuleGroup | undefined) { renderRuleGroup(title: SafeReactNode, group: RuleGroup | undefined) {
if (!group) return null; if (!group) return null;
const { rule, ranges } = group; const { rule, ranges } = group;

View File

@ -5,7 +5,7 @@
import "./radio.scss"; import "./radio.scss";
import React, { useContext, useRef } from "react"; import React, { useContext, useRef } from "react";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames, noop } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities";
export interface RadioGroupProps<T> { export interface RadioGroupProps<T> {
@ -50,7 +50,7 @@ export function RadioGroup<T>({
export interface RadioProps<T> { export interface RadioProps<T> {
className?: string; className?: string;
label: React.ReactNode; label: SafeReactNode;
value: T; value: T;
disabled?: boolean; disabled?: boolean;
} }

View File

@ -4,7 +4,7 @@
*/ */
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import type { RequestIdleCallback } from "./request-idle-callback.injectable"; import type { RequestIdleCallback } from "./request-idle-callback.injectable";
import type { CancelIdleCallback } from "./cancel-idle-callback.injectable"; import type { CancelIdleCallback } from "./cancel-idle-callback.injectable";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
@ -13,8 +13,8 @@ import requestIdleCallbackInjectable from "./request-idle-callback.injectable";
import idleCallbackTimeoutInjectable from "./idle-callback-timeout.injectable"; import idleCallbackTimeoutInjectable from "./idle-callback-timeout.injectable";
export interface RenderDelayProps { export interface RenderDelayProps {
placeholder?: React.ReactNode; placeholder?: SafeReactNode;
children: SingleOrMany<React.ReactNode>; children: SingleOrMany<SafeReactNode>;
} }
interface Dependencies { interface Dependencies {

View File

@ -14,6 +14,7 @@ import { observer } from "mobx-react";
import ReactSelect, { components, createFilter } from "react-select"; import ReactSelect, { components, createFilter } from "react-select";
import type { Props as ReactSelectProps, GroupBase, MultiValue, OptionsOrGroups, PropsValue, SingleValue } from "react-select"; import type { Props as ReactSelectProps, GroupBase, MultiValue, OptionsOrGroups, PropsValue, SingleValue } from "react-select";
import type { LensTheme } from "../../themes/lens-theme"; import type { LensTheme } from "../../themes/lens-theme";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import activeThemeInjectable from "../../themes/active.injectable"; import activeThemeInjectable from "../../themes/active.injectable";
@ -23,7 +24,7 @@ const { Menu } = components;
export interface SelectOption<Value> { export interface SelectOption<Value> {
value: Value; value: Value;
label: React.ReactNode; label: SafeReactNode;
isDisabled?: boolean; isDisabled?: boolean;
isSelected?: boolean; isSelected?: boolean;
id?: string; id?: string;

View File

@ -3,6 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { SafeReactNode } from "@k8slens/utilities";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
/** /**
@ -34,10 +35,10 @@ export interface StatusBarRegistration {
/** /**
* @deprecated use {@link StatusBarRegistration.components} instead * @deprecated use {@link StatusBarRegistration.components} instead
*/ */
item?: React.ReactNode | (() => React.ReactNode); item?: SafeReactNode | (() => SafeReactNode);
/** /**
* The newer API, allows for registering a component instead of a ReactNode * The newer API, allows for registering a component instead of a SafeReactNode
*/ */
components?: StatusBarComponents; components?: StatusBarComponents;

View File

@ -6,10 +6,12 @@
import "./status-brick.scss"; import "./status-brick.scss";
import React from "react"; import React from "react";
import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { withTooltip } from "@k8slens/tooltip"; import { withTooltip } from "@k8slens/tooltip";
export interface StatusBrickProps extends React.HTMLAttributes<HTMLDivElement> { export interface StatusBrickProps extends React.HTMLAttributes<HTMLDivElement> {
children?: SingleOrMany<SafeReactNode>;
} }
export const StatusBrick = withTooltip(({ className, ...elemProps }: StatusBrickProps) => ( export const StatusBrick = withTooltip(({ className, ...elemProps }: StatusBrickProps) => (

View File

@ -3,17 +3,18 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { SafeReactNode } from "@k8slens/utilities";
import React from "react"; import React from "react";
interface FormControlLabelProps { interface FormControlLabelProps {
control: React.ReactElement<any, any>; control: React.ReactElement<any, any>;
label: React.ReactNode; label: SafeReactNode;
} }
/** /**
* @deprecated Use <Switch/> instead from "../switch.tsx". * @deprecated Use <Switch/> instead from "../switch.tsx".
*/ */
export function FormSwitch(props: FormControlLabelProps & { children?: React.ReactNode }) { export function FormSwitch(props: FormControlLabelProps & { children?: SafeReactNode }) {
const ClonedElement = React.cloneElement(props.control, { const ClonedElement = React.cloneElement(props.control, {
children: <span>{props.label}</span>, children: <span>{props.label}</span>,
}); });

View File

@ -3,12 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { SafeReactNode } from "@k8slens/utilities";
import React from "react"; import React from "react";
import { Switch } from "./switch"; import { Switch } from "./switch";
export interface SwitcherProps { export interface SwitcherProps {
disabled?: boolean; disabled?: boolean;
children?: React.ReactNode; children?: SafeReactNode;
checked?: boolean; checked?: boolean;
onChange?: (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => void; onChange?: (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => void;
name?: string; name?: string;

View File

@ -6,8 +6,8 @@
import "./table-cell.scss"; import "./table-cell.scss";
import type { TableSortBy, TableSortParams } from "./table"; import type { TableSortBy, TableSortParams } from "./table";
import type { ReactNode } from "react";
import React from "react"; import React from "react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { Checkbox } from "../checkbox"; import { Checkbox } from "../checkbox";
@ -29,7 +29,7 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
/** /**
* The actual value of the cell * The actual value of the cell
*/ */
title?: ReactNode; title?: SafeReactNode;
/** /**
* content inside could be scrolled * content inside could be scrolled

View File

@ -7,6 +7,7 @@ import "./table.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames, isDefined } from "@k8slens/utilities"; import { cssNames, isDefined } from "@k8slens/utilities";
import type { TableRowElem, TableRowProps } from "./table-row"; import type { TableRowElem, TableRowProps } from "./table-row";
import { TableRow } from "./table-row"; import { TableRow } from "./table-row";
@ -86,7 +87,7 @@ export interface TableProps<Item> extends React.DOMAttributes<HTMLDivElement> {
/** /**
* This is shown when {@link TableProps.items} is empty * This is shown when {@link TableProps.items} is empty
*/ */
noItems?: React.ReactNode; noItems?: SafeReactNode;
/** /**
* Allows to scroll list to selected item * Allows to scroll list to selected item
*/ */

View File

@ -6,6 +6,7 @@
import "./tabs.scss"; import "./tabs.scss";
import type { DOMAttributes } from "react"; import type { DOMAttributes } from "react";
import React from "react"; import React from "react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { Icon } from "../icon"; import { Icon } from "../icon";
import autoBindReact from "auto-bind/react"; import autoBindReact from "auto-bind/react";
@ -55,8 +56,8 @@ export interface TabProps<D> extends DOMAttributes<HTMLElement> {
className?: string; className?: string;
active?: boolean; active?: boolean;
disabled?: boolean; disabled?: boolean;
icon?: React.ReactNode | string; // material-io name or custom icon icon?: SafeReactNode | string; // material-io name or custom icon
label?: React.ReactNode; label?: SafeReactNode;
value: D; value: D;
} }

View File

@ -6,6 +6,7 @@
import styles from "./tree-view.module.scss"; import styles from "./tree-view.module.scss";
import type { MouseEventHandler } from "react"; import type { MouseEventHandler } from "react";
import React, { useState } from "react"; import React, { useState } from "react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { Icon } from "../icon"; import { Icon } from "../icon";
@ -15,7 +16,7 @@ export interface TreeViewClasses {
export interface TreeViewProps { export interface TreeViewProps {
classes?: TreeViewClasses; classes?: TreeViewClasses;
children: React.ReactNode; children: SafeReactNode;
} }
export function TreeView(props: TreeViewProps) { export function TreeView(props: TreeViewProps) {

View File

@ -9,6 +9,7 @@ import moment from "moment";
import React from "react"; import React from "react";
import type { Secret } from "@k8slens/kube-object"; import type { Secret } from "@k8slens/kube-object";
import type { SafeReactNode } from "@k8slens/utilities";
import { prevDefault } from "@k8slens/utilities"; import { prevDefault } from "@k8slens/utilities";
import { Icon } from "../../icon"; import { Icon } from "../../icon";
@ -22,7 +23,7 @@ interface State {
interface RenderRowArgs { interface RenderRowArgs {
name: string; name: string;
value: React.ReactNode; value: SafeReactNode;
} }
export class ServiceAccountsSecret extends React.Component<ServiceAccountsSecretProps, State> { export class ServiceAccountsSecret extends React.Component<ServiceAccountsSecretProps, State> {

View File

@ -5,6 +5,7 @@
import "./wizard.scss"; import "./wizard.scss";
import React from "react"; import React from "react";
import type { SafeReactNode } from "@k8slens/utilities";
import { cssNames, prevDefault } from "@k8slens/utilities"; import { cssNames, prevDefault } from "@k8slens/utilities";
import { Button } from "@k8slens/button"; import { Button } from "@k8slens/button";
import { Stepper } from "../stepper"; import { Stepper } from "../stepper";
@ -24,7 +25,7 @@ export interface WizardProps<D> extends WizardCommonProps<D> {
className?: string; className?: string;
step?: number; step?: number;
title?: string; title?: string;
header?: React.ReactNode; header?: SafeReactNode;
onChange?: (step: number) => void; onChange?: (step: number) => void;
children?: React.ReactElement<WizardStepProps<D>>[] | React.ReactElement<WizardStepProps<D>>; children?: React.ReactElement<WizardStepProps<D>>[] | React.ReactElement<WizardStepProps<D>>;
} }
@ -107,28 +108,28 @@ export interface WizardStepProps<D> extends WizardCommonProps<D> {
title?: string; title?: string;
className?: string | object; className?: string | object;
contentClass?: string | object; contentClass?: string | object;
customButtons?: React.ReactNode; // render custom buttons block in footer customButtons?: SafeReactNode; // render custom buttons block in footer
moreButtons?: React.ReactNode; // add more buttons to section in the footer moreButtons?: SafeReactNode; // add more buttons to section in the footer
loading?: boolean; // indicator of loading content for the step loading?: boolean; // indicator of loading content for the step
waiting?: boolean; // indicator of waiting response before going to next step waiting?: boolean; // indicator of waiting response before going to next step
disabledNext?: boolean; // disable next button flag, e.g when filling step is not finished disabledNext?: boolean; // disable next button flag, e.g when filling step is not finished
hideNextBtn?: boolean; hideNextBtn?: boolean;
hideBackBtn?: boolean; hideBackBtn?: boolean;
step?: number; step?: number;
prevLabel?: React.ReactNode; // custom label for prev button prevLabel?: SafeReactNode; // custom label for prev button
nextLabel?: React.ReactNode; // custom label for next button nextLabel?: SafeReactNode; // custom label for next button
next?: () => void | boolean | Promise<any>; // custom action for next button next?: () => void | boolean | Promise<any>; // custom action for next button
prev?: () => void; // custom action for prev button prev?: () => void; // custom action for prev button
first?: () => void; first?: () => void;
last?: () => void; last?: () => void;
isFirst?: () => boolean; isFirst?: () => boolean;
isLast?: () => boolean; isLast?: () => boolean;
beforeContent?: React.ReactNode; beforeContent?: SafeReactNode;
afterContent?: React.ReactNode; afterContent?: SafeReactNode;
noValidate?: boolean; // no validate form attribute noValidate?: boolean; // no validate form attribute
skip?: boolean; // don't render the step skip?: boolean; // don't render the step
scrollable?: boolean; scrollable?: boolean;
children?: React.ReactNode | React.ReactNode[]; children?: SafeReactNode | SafeReactNode[];
testIdForNext?: string; testIdForNext?: string;
testIdForPrev?: string; testIdForPrev?: string;
} }

View File

@ -47,6 +47,7 @@
"devDependencies": { "devDependencies": {
"@async-fn/jest": "^1.6.4", "@async-fn/jest": "^1.6.4",
"@k8slens/typescript": "^6.5.0-alpha.2", "@k8slens/typescript": "^6.5.0-alpha.2",
"@ogre-tools/injectable-extension-for-auto-registration": "^15.9.0",
"@ogre-tools/test-utils": "^15.8.1", "@ogre-tools/test-utils": "^15.8.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"webpack-node-externals": "^3.0.0" "webpack-node-externals": "^3.0.0"

View File

@ -33,6 +33,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@k8slens/kube-object": "^1.0.0-alpha.1", "@k8slens/kube-object": "^1.0.0-alpha.1",
"@k8slens/utilities": "^1.0.0-alpha.3",
"@ogre-tools/injectable": "^15.8.1", "@ogre-tools/injectable": "^15.8.1",
"react": "^17.0.2" "react": "^17.0.2"
}, },

View File

@ -4,7 +4,7 @@
*/ */
import type { KubeObject } from "@k8slens/kube-object"; import type { KubeObject } from "@k8slens/kube-object";
import type { ReactNode } from "react"; import type { SafeReactNode } from "@k8slens/utilities";
import type { TableSortCallback, SearchFilter, TableCellProps } from "./list-layout-column"; import type { TableSortCallback, SearchFilter, TableCellProps } from "./list-layout-column";
export interface BaseKubeObjectListLayoutColumn<K extends KubeObject> { export interface BaseKubeObjectListLayoutColumn<K extends KubeObject> {
@ -13,7 +13,7 @@ export interface BaseKubeObjectListLayoutColumn<K extends KubeObject> {
sortingCallBack?: TableSortCallback<K>; sortingCallBack?: TableSortCallback<K>;
searchFilter?: SearchFilter<K>; searchFilter?: SearchFilter<K>;
header: TableCellProps | undefined | null; header: TableCellProps | undefined | null;
content: (item: K) => ReactNode | TableCellProps; content: (item: K) => SafeReactNode | TableCellProps;
} }
export interface GeneralKubeObjectListLayoutColumn extends BaseKubeObjectListLayoutColumn<KubeObject> { export interface GeneralKubeObjectListLayoutColumn extends BaseKubeObjectListLayoutColumn<KubeObject> {

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { ReactNode } from "react"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import type { SingleOrMany } from "@k8slens/utilities";
export interface ItemObject { export interface ItemObject {
getId: () => string; getId: () => string;
@ -37,7 +36,7 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
/** /**
* The actual value of the cell * The actual value of the cell
*/ */
title?: ReactNode; title?: SafeReactNode;
/** /**
* content inside could be scrolled * content inside could be scrolled

View File

@ -1,8 +1,9 @@
import type { SafeReactNode } from "@k8slens/utilities";
import { getInjectionToken } from "@ogre-tools/injectable"; import { getInjectionToken } from "@ogre-tools/injectable";
import type React from "react"; import type React from "react";
export type ReactApplicationHigherOrderComponent = React.ComponentType<{ export type ReactApplicationHigherOrderComponent = React.ComponentType<{
children: React.ReactNode; children: SafeReactNode;
}>; }>;
export const reactApplicationHigherOrderComponentInjectionToken = export const reactApplicationHigherOrderComponentInjectionToken =

View File

@ -6,11 +6,11 @@
import "./button.scss"; import "./button.scss";
import type { ButtonHTMLAttributes } from "react"; import type { ButtonHTMLAttributes } from "react";
import React from "react"; import React from "react";
import { cssNames } from "@k8slens/utilities"; import { cssNames, SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { withTooltip } from "@k8slens/tooltip"; import { withTooltip } from "@k8slens/tooltip";
export interface ButtonProps extends ButtonHTMLAttributes<any> { export interface ButtonProps extends ButtonHTMLAttributes<any> {
label?: React.ReactNode; label?: SafeReactNode;
waiting?: boolean; waiting?: boolean;
primary?: boolean; primary?: boolean;
accent?: boolean; accent?: boolean;
@ -23,6 +23,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<any> {
round?: boolean; round?: boolean;
href?: string; // render as hyperlink href?: string; // render as hyperlink
target?: "_blank"; // in case of using @href target?: "_blank"; // in case of using @href
children?: SingleOrMany<SafeReactNode>;
} }
export const Button = withTooltip((props: ButtonProps) => { export const Button = withTooltip((props: ButtonProps) => {

View File

@ -9,7 +9,7 @@ import type { ErrorInfo } from "react";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Button } from "@k8slens/button"; import { Button } from "@k8slens/button";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import type { ObservableHistory } from "mobx-observable-history"; import type { ObservableHistory } from "mobx-observable-history";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import { observableHistoryInjectionToken } from "@k8slens/routing"; import { observableHistoryInjectionToken } from "@k8slens/routing";
@ -18,7 +18,7 @@ const issuesTrackerUrl = "https://github.com/lensapp/lens/issues";
const forumsUrl = "https://forums.k8slens.dev"; const forumsUrl = "https://forums.k8slens.dev";
export interface ErrorBoundaryProps { export interface ErrorBoundaryProps {
children?: SingleOrMany<React.ReactNode>; children?: SingleOrMany<SafeReactNode>;
} }
interface State { interface State {

View File

@ -8,7 +8,7 @@ import "./tooltip.scss";
import React from "react"; import React from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { IClassName } from "@k8slens/utilities"; import type { IClassName, SafeReactNode } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities";
import { observable, makeObservable, action, runInAction } from "mobx"; import { observable, makeObservable, action, runInAction } from "mobx";
import autoBindReact from "auto-bind/react"; import autoBindReact from "auto-bind/react";
@ -35,7 +35,7 @@ export interface TooltipProps {
className?: IClassName; className?: IClassName;
formatters?: TooltipContentFormatters; formatters?: TooltipContentFormatters;
style?: React.CSSProperties; style?: React.CSSProperties;
children?: React.ReactNode; children?: SafeReactNode;
"data-testid"?: string; "data-testid"?: string;
} }

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { SingleOrMany } from "@k8slens/utilities"; import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities";
import { render, RenderResult } from "@testing-library/react"; import { render, RenderResult } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import React from "react"; import React from "react";
@ -12,7 +12,7 @@ import { withTooltip } from "./withTooltip";
type MyComponentProps = { type MyComponentProps = {
text: string; text: string;
id?: string; id?: string;
children?: SingleOrMany<React.ReactNode>; children?: SingleOrMany<SafeReactNode>;
"data-testid"?: string; "data-testid"?: string;
}; };

View File

@ -3,23 +3,22 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { ReactNode } from "react";
import React, { useState } from "react"; import React, { useState } from "react";
import type { TooltipProps } from "./tooltip"; import type { TooltipProps } from "./tooltip";
import { Tooltip } from "./tooltip"; import { Tooltip } from "./tooltip";
import { isReactNode } from "@k8slens/utilities"; import { isReactNode, SafeReactNode } from "@k8slens/utilities";
import uniqueId from "lodash/uniqueId"; import uniqueId from "lodash/uniqueId";
import type { SingleOrMany } from "@k8slens/utilities"; import type { SingleOrMany } from "@k8slens/utilities";
export interface TooltipDecoratorProps { export interface TooltipDecoratorProps {
tooltip?: ReactNode | Omit<TooltipProps, "targetId">; tooltip?: SafeReactNode | Omit<TooltipProps, "targetId">;
/** /**
* forces tooltip to detect the target's parent for mouse events. This is * forces tooltip to detect the target's parent for mouse events. This is
* useful for displaying tooltips even when the target is "disabled" * useful for displaying tooltips even when the target is "disabled"
*/ */
tooltipOverrideDisabled?: boolean; tooltipOverrideDisabled?: boolean;
id?: string; id?: string;
children?: SingleOrMany<React.ReactNode>; children?: SingleOrMany<SafeReactNode>;
} }
export function withTooltip<TargetProps>( export function withTooltip<TargetProps>(

View File

@ -17,7 +17,6 @@ export * from "./src/disposer";
export * from "./src/formatDuration"; export * from "./src/formatDuration";
export * from "./src/hash-set"; export * from "./src/hash-set";
export * from "./src/interval"; export * from "./src/interval";
export * from "./src/is-node-falsy";
export * from "./src/isMiddleClick"; export * from "./src/isMiddleClick";
export * from "./src/isReactNode"; export * from "./src/isReactNode";
export * from "./src/iter"; export * from "./src/iter";

View File

@ -1,27 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type React from "react";
/**
* Returns `true` if `node` is a falsy value
*/
export function isNodeFalsy(node: React.ReactNode): boolean {
return !isNodeRenderable(node);
}
/**
* Return `true` if React would render this
*/
export function isNodeRenderable(node: React.ReactNode): boolean {
return Boolean(node);
}
/**
* Returns the first react node provided that is would be rendered by react
*/
export function foldNodes(...nodes: React.ReactNode[]): React.ReactNode {
return nodes.find(isNodeRenderable);
}

View File

@ -3,12 +3,17 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
// Type guard for checking valid react node to use in render
import type { ReactNode } from "react";
import React from "react"; import React from "react";
import { isObject } from "./type-narrowing"; import { isObject } from "./type-narrowing";
import type { SingleOrMany } from "./types";
export function isReactNode(node: unknown): node is ReactNode { export type SafeReactNode = React.ReactElement | React.ReactText | boolean | null | undefined | Iterable<SafeReactNode>;
export function toSafeReactChildrenArray(children: SingleOrMany<SafeReactNode>) {
return React.Children.toArray(children) as (Exclude<SafeReactNode, boolean | null | undefined>)[];
}
export function isReactNode(node: unknown): node is SafeReactNode {
return (isObject(node) && React.isValidElement(node)) return (isObject(node) && React.isValidElement(node))
|| Array.isArray(node) && node.every(isReactNode) || Array.isArray(node) && node.every(isReactNode)
|| node == null || node == null

View File

@ -95,7 +95,7 @@ export function formatJSONValue(value: unknown): string {
/** /**
* This function is a safer version of `JSONPath.value(obj, path)` with untrusted jsonpath strings * This function is a safer version of `JSONPath.value(obj, path)` with untrusted jsonpath strings
* *
* This function will also stringify the value retreived from the object * This function will also stringify the value retrieved from the object
*/ */
export function safeJSONPathValue(obj: object, path: string): unknown { export function safeJSONPathValue(obj: object, path: string): unknown {
try { try {