Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/brave-ducks-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@fluentui-react-native/interactive-hooks": patch
"@fluentui-react-native/use-slot": patch
"@fluentui-react-native/button": patch
"@fluentui-react-native/switch": patch
"@fluentui-react-native/chip": patch
"@fluentui-react-native/framework-base": patch
"@fluentui-react-native/adapters": patch
---

Add a common config package with a strict tsconfig, then fix some core packages to build with stronger types
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exports[`ToggleButton default 1`] = `
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"checked": false,
"disabled": false,
"expanded": undefined,
"selected": undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ exports[`Chip component tests Chip all props 1`] = `
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"checked": false,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
Expand Down Expand Up @@ -99,7 +99,7 @@ exports[`Chip component tests Chip tokens 1`] = `
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"checked": false,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
Expand Down Expand Up @@ -169,7 +169,7 @@ exports[`Chip component tests Empty Chip 1`] = `
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"checked": false,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exports[`Switch Default 1`] = `
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"checked": false,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
Expand All @@ -29,6 +29,7 @@ exports[`Switch Default 1`] = `
}
}
accessible={true}
checked={false}
collapsable={false}
focusable={true}
onAccessibilityAction={[Function]}
Expand Down Expand Up @@ -140,7 +141,7 @@ exports[`Switch Disabled 1`] = `
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"checked": false,
"disabled": true,
"expanded": undefined,
"selected": undefined,
Expand All @@ -155,6 +156,7 @@ exports[`Switch Disabled 1`] = `
}
}
accessible={false}
checked={false}
collapsable={false}
focusable={false}
onAccessibilityAction={[Function]}
Expand Down
3 changes: 3 additions & 0 deletions packages/config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `@fluentui-react-native/config`

Shared configuration package for fluentui-react-native. Right now this is just for tsconfig files but this should start to aggregate various other configurations to make it easier to maintain things.
21 changes: 21 additions & 0 deletions packages/config/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@fluentui-react-native/config",
"version": "0.0.0",
"private": true,
"description": "Shared configuration files for Fluent UI React Native packages",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/fluentui-react-native",
"directory": "packages/configs/config"
},
"exports": {
"./tsconfig.strict.json": "./tsconfig.strict.json"
},
"dependencies": {
"@rnx-kit/tsconfig": "catalog:"
},
"furn": {
"packageType": "tooling"
}
}
7 changes: 7 additions & 0 deletions packages/config/tsconfig.strict.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@rnx-kit/tsconfig/tsconfig.node.json",
"compilerOptions": {
"outDir": "lib",
"jsx": "react-jsx"
}
}
1 change: 1 addition & 0 deletions packages/framework-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
},
"devDependencies": {
"@babel/core": "catalog:",
"@fluentui-react-native/config": "workspace:*",
"@fluentui-react-native/eslint-config-rules": "workspace:*",
"@fluentui-react-native/kit-config": "workspace:*",
"@fluentui-react-native/react-configs": "workspace:*",
Expand Down
30 changes: 16 additions & 14 deletions packages/framework-base/src/component-patterns/phasedComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@ export function getPhasedRender<TProps>(component: React.ComponentType<TProps>):
// if this has a phased render function, return it
if ((component as PhasedComponent<TProps>)._phasedRender) {
return (component as PhasedComponent<TProps>)._phasedRender;
} else if ((component as ComposableFunction<TProps>)._staged) {
} else {
// for backward compatibility check for staged render and return a wrapper that maps the signature
const staged = (component as ComposableFunction<TProps>)._staged;
return (props: TProps) => {
const { children, ...rest } = props as React.PropsWithChildren<TProps>;
const inner = staged(rest as TProps, ...React.Children.toArray(children));
// staged render functions were not consistently marking contents as composable, though they were treated
// as such in useHook. To maintain compatibility we mark the returned function as composable here. This was
// dangerous, but this shim is necessary for backward compatibility. The newer pattern is explicit about this.
if (typeof inner === 'function' && !(inner as LegacyDirectComponent<TProps>)._canCompose) {
return Object.assign(inner, { _canCompose: true });
}
return inner;
};
if (staged) {
return (props: TProps) => {
const { children, ...rest } = props as React.PropsWithChildren<TProps>;
const inner = staged(rest as TProps, ...React.Children.toArray(children));
// staged render functions were not consistently marking contents as composable, though they were treated
// as such in useHook. To maintain compatibility we mark the returned function as composable here. This was
// dangerous, but this shim is necessary for backward compatibility. The newer pattern is explicit about this.
if (typeof inner === 'function' && !(inner as LegacyDirectComponent<TProps>)._canCompose) {
return Object.assign(inner, { _canCompose: true });
}
return inner;
};
}
}
}
return undefined;
Expand All @@ -43,9 +45,9 @@ export function getPhasedRender<TProps>(component: React.ComponentType<TProps>):
*/
export function phasedComponent<TProps>(getInnerPhase: PhasedRender<TProps>): FunctionComponent<TProps> {
return Object.assign(
(props: React.PropsWithChildren<TProps>) => {
(props: TProps) => {
// pull out children from props
const { children, ...outerProps } = props;
const { children, ...outerProps } = props as React.PropsWithChildren<TProps>;
const Inner = getInnerPhase(outerProps as TProps);
return renderForJsxRuntime(Inner, { children });
},
Expand Down
11 changes: 6 additions & 5 deletions packages/framework-base/src/component-patterns/render.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import * as ReactJSX from 'react/jsx-runtime';
import type { RenderType, RenderResult, DirectComponent, LegacyDirectComponent } from './render.types';
import { extractChildren, splitPropsAndChildren } from '../utilities/typeUtils';

export type CustomRender = () => RenderResult;

Expand All @@ -20,13 +21,13 @@ function asLegacyDirectComponent<TProps>(type: RenderType): LegacyDirectComponen

export function renderForJsxRuntime<TProps>(
type: React.ElementType,
props: React.PropsWithChildren<TProps>,
props: TProps,
key?: React.Key,
jsxFn: typeof ReactJSX.jsx = undefined,
jsxFn?: typeof ReactJSX.jsx,
): RenderResult {
const legacyDirect = asLegacyDirectComponent(type);
if (legacyDirect) {
const { children, ...rest } = props;
const [rest, children] = splitPropsAndChildren(props);
const newProps = { ...rest, key };
return legacyDirect(newProps, ...React.Children.toArray(children)) as RenderResult;
}
Expand All @@ -38,14 +39,14 @@ export function renderForJsxRuntime<TProps>(

// auto-detect whether to use jsx or jsxs based on number of children, 0 or 1 = jsx, more than 1 = jsxs
if (!jsxFn) {
if (React.Children.count(props.children) > 1) {
if (React.Children.count(extractChildren(props)) > 1) {
jsxFn = ReactJSX.jsxs;
} else {
jsxFn = ReactJSX.jsx;
}
}
// Extract key from props to avoid React 19 warning about spreading key prop
// eslint-disable-next-line @typescript-eslint/no-explicit-any

const { key: propsKey, ...propsWithoutKey } = props as any;
// Use explicitly passed key, or fall back to key from props
const finalKey = key ?? propsKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export type SlotFn<TProps> = {
* Children will be passed as part of the props for component rendering. The `children` prop will be
* automatically inferred and typed correctly by the prop type.
*/
export type PhasedRender<TProps> = (props: TProps) => React.ComponentType<React.PropsWithChildren<TProps>>;
export type PhasedRender<TProps> = (props: TProps) => React.ComponentType<TProps>;

/**
* Component type for a component that can be rendered in two phases, with the attached phased render function.
Expand Down
37 changes: 16 additions & 21 deletions packages/framework-base/src/immutable-merge/Merge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,6 @@ const mergeOptions: MergeOptions = {
},
};

interface IDeepObj {
a: { b: { c: number } };
b: { c: { d: { d: string } } };
}

const deep1 = {
a: { b: { c: 1 } },
b: { c: { d: { d: 'foo' } } },
Expand Down Expand Up @@ -183,9 +178,9 @@ describe('Immutable merge unit tests', () => {
const obj1 = { a: 'a', b: 1 };
const obj2 = { b: 2, c: true };
const merged = { a: 'a', b: 2, c: true };
expect(immutableMerge<any>(obj1, obj2)).toEqual(merged);
expect(immutableMergeCore<any>(0, obj1, obj2)).toEqual(merged);
expect(immutableMergeCore<any>(true, obj1, obj2)).toEqual(merged);
expect(immutableMerge(obj1, obj2)).toEqual(merged);
expect(immutableMergeCore(0, obj1, obj2)).toEqual(merged);
expect(immutableMergeCore(true, obj1, obj2)).toEqual(merged);
});

const dm1 = {
Expand All @@ -199,23 +194,23 @@ describe('Immutable merge unit tests', () => {
};

test('deep merge', () => {
expect(immutableMerge<any>(dm1, dm2)).toEqual({
expect(immutableMerge(dm1, dm2)).toEqual({
a: { b: { c: { foo: 'foo', bar: 'bar2', baz: 'baz' } }, i: 'world' },
d: { e: 1, f: { g: 'hello', h: 2 }, j: 4 },
});
});

test('merge zero levels', () => {
expect(immutableMergeCore<any>(0, dm1, dm2)).toEqual(dm2);
expect(immutableMergeCore(0, dm1, dm2)).toEqual(dm2);
});

test('merge one level deep', () => {
const result = {
a: dm2.a,
d: { ...dm1.d, ...dm2.d },
};
expect(immutableMergeCore<any>(1, dm1, dm2)).toEqual(result);
expect(immutableMergeCore<any>({ object: 0 }, dm1, dm2)).toEqual(result);
expect(immutableMergeCore(1, dm1, dm2)).toEqual(result);
expect(immutableMergeCore({ object: 0 }, dm1, dm2)).toEqual(result);
});

test('merge with empty object', () => {
Expand All @@ -226,14 +221,14 @@ describe('Immutable merge unit tests', () => {
});

test('merge sett1 and sett2', () => {
const merged = immutableMergeCore(mergeOptions, sett1, sett2) as IFakeSettings;
const merged = immutableMergeCore(mergeOptions, sett1, sett2);
expect(merged).toEqual(sett1plus2);
expect(merged!.root.style).toBe(sett1.root.style);
expect(merged!.root!.style).toBe(sett1.root!.style);
expect(merged!.fakeSlot!.style).toBe(sett2.fakeSlot!.style);
});

test('merge sett1 and sett3', () => {
const merged = immutableMergeCore(mergeOptions, sett1, sett3) as IFakeSettings;
const merged = immutableMergeCore(mergeOptions, sett1, sett3);
expect(merged).toEqual(sett1plus3);
expect(merged!.fakeSlot).toBe(sett1.fakeSlot);
});
Expand All @@ -244,7 +239,7 @@ describe('Immutable merge unit tests', () => {
});

test('deepMerge', () => {
const merged = immutableMergeCore<any>(-1, deep1, deep2) as IDeepObj;
const merged = immutableMergeCore(-1, deep1, deep2);
expect(merged).toEqual(deepMerged);
expect(merged.b.c.d).toBe(deep1.b.c.d);
expect(merged.a.b).not.toBe(deep2.a.b);
Expand All @@ -259,14 +254,14 @@ describe('Immutable merge unit tests', () => {
const merged = processImmutable(changeMeOption1, singleToChange);
expect(merged).toEqual(singleWithChanges);
expect(merged).not.toBe(singleToChange);
expect((merged as any).b).toBe(singleToChange.b);
expect(merged.b).toBe(singleToChange.b);
});

test('single process with change - alternative', () => {
const merged = processImmutable(changeMeOption2, singleToChange);
expect(merged).toEqual(singleWithChanges);
expect(merged).not.toBe(singleToChange);
expect((merged as any).b).toBe(singleToChange.b);
expect(merged.b).toBe(singleToChange.b);
});

const withArray1 = {
Expand Down Expand Up @@ -296,15 +291,15 @@ describe('Immutable merge unit tests', () => {
};

test('last writer wins for objects and non-objects', () => {
const merged = immutableMerge<any>(withObj, withNonObj);
const merged = immutableMerge(withObj, withNonObj);
expect(merged).toEqual(withNonObj);
const merged2 = immutableMerge<any>(withNonObj, withObj);
const merged2 = immutableMerge(withNonObj, withObj);
expect(merged2).toEqual(withObj);
});

const arrayMerger = (...targets: any[]) => {
const arrays = targets.filter((t) => Array.isArray(t));
let result = [];
let result: any[] = [];
for (const v of arrays) {
if (v.length > 0) {
result = result.concat(...v);
Expand Down
Loading
Loading