@mittwald/flow-react-components 1.2.0-next.57 → 1.2.0-next.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +85 -0
- package/dist/assets/component-index.json +1 -1
- package/dist/assets/doc-properties.json +1 -1
- package/dist/js/packages/components/src/components/Popover/Popover.mjs +18 -7
- package/dist/js/packages/components/src/components/Popover/Popover.mjs.map +1 -1
- package/dist/js/packages/components/src/lib/controller/overlay/OverlayController.mjs +24 -1
- package/dist/js/packages/components/src/lib/controller/overlay/OverlayController.mjs.map +1 -1
- package/dist/types/components/Popover/Popover.d.ts +19 -0
- package/dist/types/lib/controller/overlay/OverlayController.d.ts +13 -0
- package/package.json +9 -9
|
@@ -1,31 +1,42 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
/* */
|
|
3
3
|
import { flowComponent } from "../../lib/componentFactory/flowComponent.mjs";
|
|
4
|
+
import { useWarnDeprecation } from "../DeprecationWarningProvider/DeprecationWarningProvider.mjs";
|
|
4
5
|
import { useOverlayController } from "../../lib/controller/overlay/useOverlayController.mjs";
|
|
5
6
|
import { OverlayContextProvider } from "../../lib/controller/overlay/OverlayContextProvider.mjs";
|
|
6
7
|
import Popover_module_default from "./Popover.module.mjs";
|
|
7
8
|
import PopoverContentView from "../../views/PopoverContentView.mjs";
|
|
8
9
|
import clsx from "clsx";
|
|
9
|
-
import "react";
|
|
10
|
+
import { useCallback, useEffect } from "react";
|
|
10
11
|
import { jsx } from "react/jsx-runtime";
|
|
11
12
|
//#region src/components/Popover/Popover.tsx
|
|
12
13
|
var Popover = flowComponent("Popover", (props) => {
|
|
13
|
-
const { children, className, controller: controllerFromProps, onOpenChange: onOpenChangeFromProps,
|
|
14
|
+
const { children, className, controller: controllerFromProps, onOpenChange: onOpenChangeFromProps, isOpen: isOpenFromProps, isDefaultOpen, defaultOpen, ...contentProps } = props;
|
|
15
|
+
const warnDeprecation = useWarnDeprecation();
|
|
16
|
+
if (defaultOpen !== void 0) warnDeprecation("The 'defaultOpen' prop is deprecated and will be removed in a future release. Use 'isDefaultOpen' instead.");
|
|
14
17
|
const controllerFromContext = useOverlayController("Popover", {
|
|
15
18
|
reuseControllerFromContext: true,
|
|
16
|
-
isDefaultOpen: defaultOpen
|
|
19
|
+
isDefaultOpen: isDefaultOpen ?? defaultOpen ?? false
|
|
17
20
|
});
|
|
18
21
|
const controller = controllerFromProps ?? controllerFromContext;
|
|
22
|
+
const notifyOpenChange = useCallback((isOpen) => {
|
|
23
|
+
onOpenChangeFromProps?.(isOpen);
|
|
24
|
+
}, [onOpenChangeFromProps]);
|
|
25
|
+
controller.useUpdateOptions({ onOpenChange: notifyOpenChange });
|
|
19
26
|
const isOpen = controller.useIsOpen();
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (isOpenFromProps !== void 0 && isOpenFromProps !== isOpen) controller.syncOpen(isOpenFromProps);
|
|
29
|
+
}, [
|
|
30
|
+
controller,
|
|
31
|
+
isOpenFromProps,
|
|
32
|
+
isOpen
|
|
33
|
+
]);
|
|
20
34
|
const rootClassName = clsx(Popover_module_default.popover, className);
|
|
21
35
|
return /* @__PURE__ */ jsx(PopoverContentView, {
|
|
22
36
|
...contentProps,
|
|
23
37
|
className: rootClassName,
|
|
24
38
|
isOpen,
|
|
25
|
-
onOpenChange:
|
|
26
|
-
if (!onOpenChangeFromProps) controller.setOpen(isOpen);
|
|
27
|
-
else onOpenChangeFromProps(isOpen);
|
|
28
|
-
},
|
|
39
|
+
onOpenChange: controller.setOpen,
|
|
29
40
|
children: /* @__PURE__ */ jsx(OverlayContextProvider, {
|
|
30
41
|
type: "Popover",
|
|
31
42
|
controller,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.mjs","names":[],"sources":["../../../../../../../src/components/Popover/Popover.tsx"],"sourcesContent":["import { type PropsWithChildren } from \"react\";\nimport type * as Aria from \"react-aria-components\";\nimport clsx from \"clsx\";\nimport { type OverlayController, useOverlayController } from \"@/lib/controller\";\nimport {\n flowComponent,\n type FlowComponentProps,\n} from \"@/lib/componentFactory/flowComponent\";\nimport OverlayContextProvider from \"@/lib/controller/overlay/OverlayContextProvider\";\nimport styles from \"./Popover.module.scss\";\nimport PopoverContentView from \"@/views/PopoverContentView\";\n\nexport interface PopoverProps\n extends\n PropsWithChildren<Omit<Aria.PopoverProps, \"children\" | \"ref\">>,\n FlowComponentProps<HTMLDivElement> {\n /**\n * Whether the popover should display a tip, pointing towards the trigger\n * element.\n */\n withTip?: boolean;\n /** Whether the popover contains a dialog. */\n isDialogContent?: boolean;\n /** An overlay controller to control the popover state. */\n controller?: OverlayController;\n /** A fixed width for the popover. */\n width?: string | number;\n}\n\nexport const Popover = flowComponent(\"Popover\", (props) => {\n const {\n children,\n className,\n controller: controllerFromProps,\n onOpenChange: onOpenChangeFromProps,\n
|
|
1
|
+
{"version":3,"file":"Popover.mjs","names":[],"sources":["../../../../../../../src/components/Popover/Popover.tsx"],"sourcesContent":["import { type PropsWithChildren, useCallback, useEffect } from \"react\";\nimport type * as Aria from \"react-aria-components\";\nimport clsx from \"clsx\";\nimport { type OverlayController, useOverlayController } from \"@/lib/controller\";\nimport {\n flowComponent,\n type FlowComponentProps,\n} from \"@/lib/componentFactory/flowComponent\";\nimport OverlayContextProvider from \"@/lib/controller/overlay/OverlayContextProvider\";\nimport { useWarnDeprecation } from \"@/components/DeprecationWarningProvider\";\nimport styles from \"./Popover.module.scss\";\nimport PopoverContentView from \"@/views/PopoverContentView\";\n\nexport interface PopoverProps\n extends\n PropsWithChildren<Omit<Aria.PopoverProps, \"children\" | \"ref\">>,\n FlowComponentProps<HTMLDivElement> {\n /**\n * Whether the popover should display a tip, pointing towards the trigger\n * element.\n */\n withTip?: boolean;\n /** Whether the popover contains a dialog. */\n isDialogContent?: boolean;\n /** An overlay controller to control the popover state. */\n controller?: OverlayController;\n /** A fixed width for the popover. */\n width?: string | number;\n /**\n * Whether the popover is open. Use it to control the popover state – then\n * `onOpenChange` must update the state this value comes from.\n */\n isOpen?: boolean;\n /**\n * Whether the popover is open initially. Use it for an uncontrolled popover.\n *\n * @default false\n */\n isDefaultOpen?: boolean;\n /** @deprecated Use `isDefaultOpen` instead. */\n defaultOpen?: boolean;\n /**\n * Called with the new open state whenever the popover is opened or closed –\n * on every path, including a close triggered through the controller. It only\n * reports the change; it never performs or suppresses it.\n */\n onOpenChange?: (isOpen: boolean) => void;\n}\n\nexport const Popover = flowComponent(\"Popover\", (props) => {\n const {\n children,\n className,\n controller: controllerFromProps,\n onOpenChange: onOpenChangeFromProps,\n isOpen: isOpenFromProps,\n isDefaultOpen,\n defaultOpen,\n ...contentProps\n } = props;\n\n const warnDeprecation = useWarnDeprecation();\n\n if (defaultOpen !== undefined) {\n warnDeprecation(\n \"The 'defaultOpen' prop is deprecated and will be removed in a future release. Use 'isDefaultOpen' instead.\",\n );\n }\n\n const controllerFromContext = useOverlayController(\"Popover\", {\n reuseControllerFromContext: true,\n isDefaultOpen: isDefaultOpen ?? defaultOpen ?? false,\n });\n\n const controller = controllerFromProps ?? controllerFromContext;\n\n // The return value is dropped on purpose – the prop is typed to return\n // nothing and must not gain a controller handler's abort semantics.\n const notifyOpenChange = useCallback(\n (isOpen: boolean) => {\n onOpenChangeFromProps?.(isOpen);\n },\n [onOpenChangeFromProps],\n );\n\n controller.useUpdateOptions({ onOpenChange: notifyOpenChange });\n\n const isOpen = controller.useIsOpen();\n\n useEffect(() => {\n if (isOpenFromProps !== undefined && isOpenFromProps !== isOpen) {\n controller.syncOpen(isOpenFromProps);\n }\n }, [controller, isOpenFromProps, isOpen]);\n\n const rootClassName = clsx(styles.popover, className);\n\n return (\n <PopoverContentView\n {...contentProps}\n className={rootClassName}\n isOpen={isOpen}\n onOpenChange={controller.setOpen}\n >\n <OverlayContextProvider type=\"Popover\" controller={controller}>\n {children}\n </OverlayContextProvider>\n </PopoverContentView>\n );\n});\n\nexport default Popover;\n"],"mappings":";;;;;;;;;;;;AAiDA,IAAa,UAAU,cAAc,YAAY,UAAU;CACzD,MAAM,EACJ,UACA,WACA,YAAY,qBACZ,cAAc,uBACd,QAAQ,iBACR,eACA,aACA,GAAG,iBACD;CAEJ,MAAM,kBAAkB,mBAAmB;CAE3C,IAAI,gBAAgB,KAAA,GAClB,gBACE,4GACF;CAGF,MAAM,wBAAwB,qBAAqB,WAAW;EAC5D,4BAA4B;EAC5B,eAAe,iBAAiB,eAAe;CACjD,CAAC;CAED,MAAM,aAAa,uBAAuB;CAI1C,MAAM,mBAAmB,aACtB,WAAoB;EACnB,wBAAwB,MAAM;CAChC,GACA,CAAC,qBAAqB,CACxB;CAEA,WAAW,iBAAiB,EAAE,cAAc,iBAAiB,CAAC;CAE9D,MAAM,SAAS,WAAW,UAAU;CAEpC,gBAAgB;EACd,IAAI,oBAAoB,KAAA,KAAa,oBAAoB,QACvD,WAAW,SAAS,eAAe;CAEvC,GAAG;EAAC;EAAY;EAAiB;CAAM,CAAC;CAExC,MAAM,gBAAgB,KAAK,uBAAO,SAAS,SAAS;CAEpD,OACE,oBAAC,oBAAD;EACE,GAAI;EACJ,WAAW;EACH;EACR,cAAc,WAAW;EAEzB,UAAA,oBAAC,wBAAD;GAAwB,MAAK;GAAsB;GAChD;EACqB,CAAA;CACN,CAAA;AAExB,CAAC"}
|
|
@@ -30,6 +30,12 @@ var OverlayController = class OverlayController {
|
|
|
30
30
|
* armed.
|
|
31
31
|
*/
|
|
32
32
|
closeWithoutConfirmationGrants = /* @__PURE__ */ new Set();
|
|
33
|
+
/**
|
|
34
|
+
* The open state a `setOpen` call is currently applying. Handlers run before
|
|
35
|
+
* `isOpen` is assigned, so a handler that calls `setOpen` again would not be
|
|
36
|
+
* caught by the equality check at the top and would recurse.
|
|
37
|
+
*/
|
|
38
|
+
openStateInFlight;
|
|
33
39
|
constructor(options = {}) {
|
|
34
40
|
makeObservable(this, {
|
|
35
41
|
isOpen: observable,
|
|
@@ -40,6 +46,7 @@ var OverlayController = class OverlayController {
|
|
|
40
46
|
close: action.bound,
|
|
41
47
|
toggle: action.bound,
|
|
42
48
|
setOpen: action.bound,
|
|
49
|
+
syncOpen: action.bound,
|
|
43
50
|
setIsContentSuspended: action.bound,
|
|
44
51
|
confirmClose: action.bound,
|
|
45
52
|
cancelConfirmation: action.bound
|
|
@@ -152,7 +159,23 @@ var OverlayController = class OverlayController {
|
|
|
152
159
|
this.setOpen(!this.isOpen);
|
|
153
160
|
}
|
|
154
161
|
setOpen(toOpen, options = {}) {
|
|
155
|
-
if (this.isOpen === toOpen) return;
|
|
162
|
+
if (this.isOpen === toOpen || this.openStateInFlight === toOpen) return;
|
|
163
|
+
this.openStateInFlight = toOpen;
|
|
164
|
+
try {
|
|
165
|
+
this.applyOpenState(toOpen, options);
|
|
166
|
+
} finally {
|
|
167
|
+
this.openStateInFlight = void 0;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Sets the open state without running any handler. Use it to mirror a
|
|
172
|
+
* controlled `isOpen` prop, whose change comes from the consumer – notifying
|
|
173
|
+
* them of their own state would echo it back.
|
|
174
|
+
*/
|
|
175
|
+
syncOpen(toOpen) {
|
|
176
|
+
this.isOpen = toOpen;
|
|
177
|
+
}
|
|
178
|
+
applyOpenState(toOpen, options) {
|
|
156
179
|
const { bypassConfirmation = false } = options;
|
|
157
180
|
if (toOpen === false) {
|
|
158
181
|
const closeIsConfirmed = this.closeIsConfirmed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OverlayController.mjs","names":[],"sources":["../../../../../../../../src/lib/controller/overlay/OverlayController.ts"],"sourcesContent":["import {\n action,\n computed,\n makeObservable,\n observable,\n runInAction,\n} from \"mobx\";\nimport useSelector from \"@/lib/mobx/useSelector\";\nimport { useStatic } from \"@/lib/hooks/useStatic\";\nimport { useEffect, useRef, type DependencyList } from \"react\";\nimport { useCloseOverlayConfirmationController } from \"@/lib/controller/overlay/useCloseOverlayConfirmationController\";\nimport type { FlowComponentName } from \"@/components/propTypes\";\n\nexport type OverlayOpenHandler = () => unknown;\nexport type OverlayCloseHandler = () => unknown;\nexport type OverlayOpenStateHandler = (isOpen: boolean) => unknown;\ntype AnyOverlayOpenStateHandler =\n OverlayOpenHandler | OverlayCloseHandler | OverlayOpenStateHandler;\n\ntype DisposerFn = () => void;\n\nexport interface CloseOverlayOptions {\n overlay: FlowComponentName | OverlayController;\n bypassConfirmation?: boolean;\n}\n\nexport interface OnOverlayClosedOptions {\n dependencies?: DependencyList;\n}\n\nexport type CloseModalOptions = Omit<CloseOverlayOptions, \"overlay\">;\n\ntype CloseOptions = CloseOverlayOptions | CloseModalOptions;\n\nexport interface OverlayControllerOptions {\n isDefaultOpen?: boolean;\n onOpen?: OverlayOpenHandler;\n onClose?: OverlayCloseHandler;\n onOpenChange?: OverlayOpenStateHandler;\n /**\n * Whether closing the overlay must be confirmed. `undefined` means \"no\n * opinion\": the value is not contributed at all, so other sources (e.g. a\n * `Modal`'s `confirmOnClose` prop or a dirty `Form`) still decide.\n */\n confirmOnClose?: boolean;\n}\n\ntype ConstructorOptions = Pick<\n OverlayControllerOptions,\n \"isDefaultOpen\" | \"confirmOnClose\"\n>;\n\nexport class OverlayController {\n public isOpen = false;\n public isContentSuspended = false;\n private onOpenHandlers = new Set<OverlayOpenHandler>();\n private onCloseHandlers = new Set<OverlayCloseHandler>();\n private onOpenChangeHandlers = new Set<OverlayOpenStateHandler>();\n\n public showConfirmationModal = false;\n public closeIsConfirmed = false;\n private readonly confirmOnCloseFromOptions: boolean;\n /**\n * Every mounted component that has an opinion on close confirmation\n * contributes one entry here – e.g. a `Modal` with a `confirmOnClose` prop\n * and a `Form` tracking its dirty state. Combining them instead of assigning\n * a single flag keeps them from overwriting each other.\n */\n private confirmOnCloseSources = observable.map<object, boolean>();\n /**\n * Active grants that allow closing without a confirmation. Each entry is\n * scoped to an ongoing operation that legitimately closes the overlay – e.g.\n * a `Form` submit – and is dropped again when that operation has finished, so\n * an operation that does _not_ close the overlay leaves the confirmation\n * armed.\n */\n private closeWithoutConfirmationGrants = new Set<object>();\n\n public constructor(options: ConstructorOptions = {}) {\n makeObservable(this, {\n isOpen: observable,\n isContentSuspended: observable,\n showConfirmationModal: observable,\n confirmOnCloseEnabled: computed,\n open: action.bound,\n close: action.bound,\n toggle: action.bound,\n setOpen: action.bound,\n setIsContentSuspended: action.bound,\n confirmClose: action.bound,\n cancelConfirmation: action.bound,\n });\n const { isDefaultOpen = false, confirmOnClose = false } = options;\n this.isOpen = isDefaultOpen;\n this.confirmOnCloseFromOptions = confirmOnClose;\n }\n\n /** Whether closing this overlay currently requires a confirmation. */\n public get confirmOnCloseEnabled(): boolean {\n if (this.confirmOnCloseSources.size === 0) {\n return this.confirmOnCloseFromOptions;\n }\n return Array.from(this.confirmOnCloseSources.values()).some(Boolean);\n }\n\n public useUpdateOptions(options: OverlayControllerOptions = {}): void {\n const { onOpen, onClose, onOpenChange, confirmOnClose } = options;\n\n this.useOnHandler(onOpen, (h) =>\n this.addOpenStateHandler(h, this.onOpenHandlers),\n );\n this.useOnHandler(onClose, (h) =>\n this.addOpenStateHandler(h, this.onCloseHandlers),\n );\n this.useOnHandler(onOpenChange, (h) =>\n this.addOpenStateHandler(h, this.onOpenChangeHandlers),\n );\n\n this.useConfirmOnCloseSource(confirmOnClose);\n }\n\n /**\n * Registers the calling component as a close confirmation source for as long\n * as it is mounted. `undefined` contributes nothing at all.\n */\n private useConfirmOnCloseSource(confirmOnClose: boolean | undefined): void {\n const source = useStatic((): object => ({}));\n\n useEffect(() => {\n if (confirmOnClose === undefined) {\n this.removeConfirmOnCloseSource(source);\n return;\n }\n this.setConfirmOnCloseSource(source, confirmOnClose);\n return () => this.removeConfirmOnCloseSource(source);\n }, [this, source, confirmOnClose]);\n }\n\n private setConfirmOnCloseSource(source: object, confirmOnClose: boolean) {\n runInAction(() => this.confirmOnCloseSources.set(source, confirmOnClose));\n }\n\n private removeConfirmOnCloseSource(source: object) {\n runInAction(() => this.confirmOnCloseSources.delete(source));\n }\n\n /**\n * Can be used to execute a callback when the Overlay has unmounted – means\n * after any closing animation.\n *\n * NOTICE: This hook only works inside the corresponding Overlay!\n */\n public useOnClosed(\n callback: () => unknown,\n options: OnOverlayClosedOptions = {},\n ) {\n const { dependencies = [] } = options;\n const isOpen = this.useIsOpen();\n const wasOpen = useRef(this.isOpen);\n\n this.useUpdateOptions({\n onOpen: () => {\n wasOpen.current = true;\n },\n });\n\n useEffect(() => {\n return () => {\n if (!isOpen && wasOpen.current) {\n wasOpen.current = false;\n callback();\n }\n };\n }, [isOpen, ...dependencies]);\n }\n\n public static useNew(\n options: OverlayControllerOptions = {},\n ): OverlayController {\n const controller = useStatic(() => new OverlayController(options));\n controller.useUpdateOptions(options);\n return controller;\n }\n\n private addOpenStateHandler<T extends AnyOverlayOpenStateHandler>(\n handler: T,\n handlersSet: Set<T>,\n ): DisposerFn {\n handlersSet.add(handler);\n return () => {\n handlersSet.delete(handler);\n };\n }\n\n private useOnHandler<T extends AnyOverlayOpenStateHandler>(\n handler: T | undefined,\n addHandlerFn: (handler: T) => DisposerFn,\n ) {\n useEffect(\n () => (handler ? addHandlerFn(handler) : undefined),\n [handler, this],\n );\n }\n\n private executeHandlers(\n isOpen: boolean,\n handlers: Set<\n OverlayOpenHandler | OverlayCloseHandler | OverlayOpenStateHandler\n >,\n ): boolean {\n const handlerResult = Array.from(handlers).map((handler) =>\n handler(isOpen),\n );\n return handlerResult.some((result) => result === false);\n }\n\n private executeOnClose(): boolean {\n return this.executeHandlers(false, this.onCloseHandlers);\n }\n\n private executeOnOpen(): boolean {\n return this.executeHandlers(true, this.onOpenHandlers);\n }\n\n private executeOnOpenChange(isOpen: boolean): boolean {\n return this.executeHandlers(isOpen, this.onOpenChangeHandlers);\n }\n\n public addOnClose(handler: OverlayCloseHandler) {\n return this.addOpenStateHandler(handler, this.onCloseHandlers);\n }\n\n public addOnOpen(handler: OverlayOpenHandler) {\n return this.addOpenStateHandler(handler, this.onOpenHandlers);\n }\n\n public addOnOpenChange(handler: OverlayOpenStateHandler) {\n return this.addOpenStateHandler(handler, this.onOpenChangeHandlers);\n }\n\n public open(): void {\n this.setOpen(true);\n }\n\n public close(options?: CloseOptions): void {\n this.setOpen(false, options);\n }\n\n public toggle(): void {\n this.setOpen(!this.isOpen);\n }\n\n public setOpen(toOpen: boolean, options: CloseOptions = {}): void {\n if (this.isOpen === toOpen) {\n return;\n }\n\n const { bypassConfirmation = false } = options;\n\n if (toOpen === false) {\n // The confirmation is a one-shot permission for exactly this close\n // attempt – consume it here instead of only when a close succeeds, so it\n // cannot survive an aborted close and disarm later attempts.\n const closeIsConfirmed = this.closeIsConfirmed;\n this.closeIsConfirmed = false;\n\n if (\n this.confirmOnCloseEnabled &&\n !closeIsConfirmed &&\n this.closeWithoutConfirmationGrants.size === 0 &&\n !bypassConfirmation\n ) {\n this.showConfirmationModal = true;\n return;\n }\n }\n\n let aborted: boolean;\n if (toOpen) {\n aborted = this.executeOnOpen();\n } else {\n aborted = this.executeOnClose();\n }\n if (!aborted) {\n aborted = this.executeOnOpenChange(toOpen);\n }\n\n if (!aborted) {\n this.isOpen = toOpen;\n }\n }\n\n public setIsContentSuspended(to: boolean): void {\n this.isContentSuspended = to;\n }\n\n public useIsOpen() {\n return useSelector(() => this.isOpen);\n }\n\n public useIsContentSuspended() {\n return useSelector(() => this.isContentSuspended);\n }\n\n public useShowConfirmationModal() {\n return useSelector(() => this.showConfirmationModal);\n }\n\n public useConfirmOnCloseEnabled() {\n return useSelector(() => this.confirmOnCloseEnabled);\n }\n\n public useConfirmationController() {\n return useCloseOverlayConfirmationController(this);\n }\n\n /**\n * Allows closing this overlay without a confirmation until the returned\n * disposer is called. Use it around an operation that may legitimately close\n * the overlay – e.g. a `Form` submit, where a \"discard unsaved changes?\"\n * prompt would be nonsense – and dispose it as soon as that operation has\n * finished. An operation that ends without closing the overlay therefore\n * leaves the confirmation armed (#2775).\n */\n public grantCloseWithoutConfirmation(): DisposerFn {\n const grant = {};\n this.closeWithoutConfirmationGrants.add(grant);\n return () => {\n this.closeWithoutConfirmationGrants.delete(grant);\n };\n }\n\n public confirmClose(): void {\n this.closeIsConfirmed = true;\n this.showConfirmationModal = false;\n }\n\n public cancelConfirmation(): void {\n this.showConfirmationModal = false;\n }\n}\n"],"mappings":";;;;;;;;AAoDA,IAAa,oBAAb,MAAa,kBAAkB;CAC7B,SAAgB;CAChB,qBAA4B;CAC5B,iCAAyB,IAAI,IAAwB;CACrD,kCAA0B,IAAI,IAAyB;CACvD,uCAA+B,IAAI,IAA6B;CAEhE,wBAA+B;CAC/B,mBAA0B;CAC1B;;;;;;;CAOA,wBAAgC,WAAW,IAAqB;;;;;;;;CAQhE,iDAAyC,IAAI,IAAY;CAEzD,YAAmB,UAA8B,CAAC,GAAG;EACnD,eAAe,MAAM;GACnB,QAAQ;GACR,oBAAoB;GACpB,uBAAuB;GACvB,uBAAuB;GACvB,MAAM,OAAO;GACb,OAAO,OAAO;GACd,QAAQ,OAAO;GACf,SAAS,OAAO;GAChB,uBAAuB,OAAO;GAC9B,cAAc,OAAO;GACrB,oBAAoB,OAAO;EAC7B,CAAC;EACD,MAAM,EAAE,gBAAgB,OAAO,iBAAiB,UAAU;EAC1D,KAAK,SAAS;EACd,KAAK,4BAA4B;CACnC;;CAGA,IAAW,wBAAiC;EAC1C,IAAI,KAAK,sBAAsB,SAAS,GACtC,OAAO,KAAK;EAEd,OAAO,MAAM,KAAK,KAAK,sBAAsB,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO;CACrE;CAEA,iBAAwB,UAAoC,CAAC,GAAS;EACpE,MAAM,EAAE,QAAQ,SAAS,cAAc,mBAAmB;EAE1D,KAAK,aAAa,SAAS,MACzB,KAAK,oBAAoB,GAAG,KAAK,cAAc,CACjD;EACA,KAAK,aAAa,UAAU,MAC1B,KAAK,oBAAoB,GAAG,KAAK,eAAe,CAClD;EACA,KAAK,aAAa,eAAe,MAC/B,KAAK,oBAAoB,GAAG,KAAK,oBAAoB,CACvD;EAEA,KAAK,wBAAwB,cAAc;CAC7C;;;;;CAMA,wBAAgC,gBAA2C;EACzE,MAAM,SAAS,iBAAyB,CAAC,EAAE;EAE3C,gBAAgB;GACd,IAAI,mBAAmB,KAAA,GAAW;IAChC,KAAK,2BAA2B,MAAM;IACtC;GACF;GACA,KAAK,wBAAwB,QAAQ,cAAc;GACnD,aAAa,KAAK,2BAA2B,MAAM;EACrD,GAAG;GAAC;GAAM;GAAQ;EAAc,CAAC;CACnC;CAEA,wBAAgC,QAAgB,gBAAyB;EACvE,kBAAkB,KAAK,sBAAsB,IAAI,QAAQ,cAAc,CAAC;CAC1E;CAEA,2BAAmC,QAAgB;EACjD,kBAAkB,KAAK,sBAAsB,OAAO,MAAM,CAAC;CAC7D;;;;;;;CAQA,YACE,UACA,UAAkC,CAAC,GACnC;EACA,MAAM,EAAE,eAAe,CAAC,MAAM;EAC9B,MAAM,SAAS,KAAK,UAAU;EAC9B,MAAM,UAAU,OAAO,KAAK,MAAM;EAElC,KAAK,iBAAiB,EACpB,cAAc;GACZ,QAAQ,UAAU;EACpB,EACF,CAAC;EAED,gBAAgB;GACd,aAAa;IACX,IAAI,CAAC,UAAU,QAAQ,SAAS;KAC9B,QAAQ,UAAU;KAClB,SAAS;IACX;GACF;EACF,GAAG,CAAC,QAAQ,GAAG,YAAY,CAAC;CAC9B;CAEA,OAAc,OACZ,UAAoC,CAAC,GAClB;EACnB,MAAM,aAAa,gBAAgB,IAAI,kBAAkB,OAAO,CAAC;EACjE,WAAW,iBAAiB,OAAO;EACnC,OAAO;CACT;CAEA,oBACE,SACA,aACY;EACZ,YAAY,IAAI,OAAO;EACvB,aAAa;GACX,YAAY,OAAO,OAAO;EAC5B;CACF;CAEA,aACE,SACA,cACA;EACA,gBACS,UAAU,aAAa,OAAO,IAAI,KAAA,GACzC,CAAC,SAAS,IAAI,CAChB;CACF;CAEA,gBACE,QACA,UAGS;EAIT,OAHsB,MAAM,KAAK,QAAQ,CAAC,CAAC,KAAK,YAC9C,QAAQ,MAAM,CAET,CAAA,CAAc,MAAM,WAAW,WAAW,KAAK;CACxD;CAEA,iBAAkC;EAChC,OAAO,KAAK,gBAAgB,OAAO,KAAK,eAAe;CACzD;CAEA,gBAAiC;EAC/B,OAAO,KAAK,gBAAgB,MAAM,KAAK,cAAc;CACvD;CAEA,oBAA4B,QAA0B;EACpD,OAAO,KAAK,gBAAgB,QAAQ,KAAK,oBAAoB;CAC/D;CAEA,WAAkB,SAA8B;EAC9C,OAAO,KAAK,oBAAoB,SAAS,KAAK,eAAe;CAC/D;CAEA,UAAiB,SAA6B;EAC5C,OAAO,KAAK,oBAAoB,SAAS,KAAK,cAAc;CAC9D;CAEA,gBAAuB,SAAkC;EACvD,OAAO,KAAK,oBAAoB,SAAS,KAAK,oBAAoB;CACpE;CAEA,OAAoB;EAClB,KAAK,QAAQ,IAAI;CACnB;CAEA,MAAa,SAA8B;EACzC,KAAK,QAAQ,OAAO,OAAO;CAC7B;CAEA,SAAsB;EACpB,KAAK,QAAQ,CAAC,KAAK,MAAM;CAC3B;CAEA,QAAe,QAAiB,UAAwB,CAAC,GAAS;EAChE,IAAI,KAAK,WAAW,QAClB;EAGF,MAAM,EAAE,qBAAqB,UAAU;EAEvC,IAAI,WAAW,OAAO;GAIpB,MAAM,mBAAmB,KAAK;GAC9B,KAAK,mBAAmB;GAExB,IACE,KAAK,yBACL,CAAC,oBACD,KAAK,+BAA+B,SAAS,KAC7C,CAAC,oBACD;IACA,KAAK,wBAAwB;IAC7B;GACF;EACF;EAEA,IAAI;EACJ,IAAI,QACF,UAAU,KAAK,cAAc;OAE7B,UAAU,KAAK,eAAe;EAEhC,IAAI,CAAC,SACH,UAAU,KAAK,oBAAoB,MAAM;EAG3C,IAAI,CAAC,SACH,KAAK,SAAS;CAElB;CAEA,sBAA6B,IAAmB;EAC9C,KAAK,qBAAqB;CAC5B;CAEA,YAAmB;EACjB,OAAO,kBAAkB,KAAK,MAAM;CACtC;CAEA,wBAA+B;EAC7B,OAAO,kBAAkB,KAAK,kBAAkB;CAClD;CAEA,2BAAkC;EAChC,OAAO,kBAAkB,KAAK,qBAAqB;CACrD;CAEA,2BAAkC;EAChC,OAAO,kBAAkB,KAAK,qBAAqB;CACrD;CAEA,4BAAmC;EACjC,OAAO,sCAAsC,IAAI;CACnD;;;;;;;;;CAUA,gCAAmD;EACjD,MAAM,QAAQ,CAAC;EACf,KAAK,+BAA+B,IAAI,KAAK;EAC7C,aAAa;GACX,KAAK,+BAA+B,OAAO,KAAK;EAClD;CACF;CAEA,eAA4B;EAC1B,KAAK,mBAAmB;EACxB,KAAK,wBAAwB;CAC/B;CAEA,qBAAkC;EAChC,KAAK,wBAAwB;CAC/B;AACF"}
|
|
1
|
+
{"version":3,"file":"OverlayController.mjs","names":[],"sources":["../../../../../../../../src/lib/controller/overlay/OverlayController.ts"],"sourcesContent":["import {\n action,\n computed,\n makeObservable,\n observable,\n runInAction,\n} from \"mobx\";\nimport useSelector from \"@/lib/mobx/useSelector\";\nimport { useStatic } from \"@/lib/hooks/useStatic\";\nimport { useEffect, useRef, type DependencyList } from \"react\";\nimport { useCloseOverlayConfirmationController } from \"@/lib/controller/overlay/useCloseOverlayConfirmationController\";\nimport type { FlowComponentName } from \"@/components/propTypes\";\n\nexport type OverlayOpenHandler = () => unknown;\nexport type OverlayCloseHandler = () => unknown;\nexport type OverlayOpenStateHandler = (isOpen: boolean) => unknown;\ntype AnyOverlayOpenStateHandler =\n OverlayOpenHandler | OverlayCloseHandler | OverlayOpenStateHandler;\n\ntype DisposerFn = () => void;\n\nexport interface CloseOverlayOptions {\n overlay: FlowComponentName | OverlayController;\n bypassConfirmation?: boolean;\n}\n\nexport interface OnOverlayClosedOptions {\n dependencies?: DependencyList;\n}\n\nexport type CloseModalOptions = Omit<CloseOverlayOptions, \"overlay\">;\n\ntype CloseOptions = CloseOverlayOptions | CloseModalOptions;\n\nexport interface OverlayControllerOptions {\n isDefaultOpen?: boolean;\n onOpen?: OverlayOpenHandler;\n onClose?: OverlayCloseHandler;\n onOpenChange?: OverlayOpenStateHandler;\n /**\n * Whether closing the overlay must be confirmed. `undefined` means \"no\n * opinion\": the value is not contributed at all, so other sources (e.g. a\n * `Modal`'s `confirmOnClose` prop or a dirty `Form`) still decide.\n */\n confirmOnClose?: boolean;\n}\n\ntype ConstructorOptions = Pick<\n OverlayControllerOptions,\n \"isDefaultOpen\" | \"confirmOnClose\"\n>;\n\nexport class OverlayController {\n public isOpen = false;\n public isContentSuspended = false;\n private onOpenHandlers = new Set<OverlayOpenHandler>();\n private onCloseHandlers = new Set<OverlayCloseHandler>();\n private onOpenChangeHandlers = new Set<OverlayOpenStateHandler>();\n\n public showConfirmationModal = false;\n public closeIsConfirmed = false;\n private readonly confirmOnCloseFromOptions: boolean;\n /**\n * Every mounted component that has an opinion on close confirmation\n * contributes one entry here – e.g. a `Modal` with a `confirmOnClose` prop\n * and a `Form` tracking its dirty state. Combining them instead of assigning\n * a single flag keeps them from overwriting each other.\n */\n private confirmOnCloseSources = observable.map<object, boolean>();\n /**\n * Active grants that allow closing without a confirmation. Each entry is\n * scoped to an ongoing operation that legitimately closes the overlay – e.g.\n * a `Form` submit – and is dropped again when that operation has finished, so\n * an operation that does _not_ close the overlay leaves the confirmation\n * armed.\n */\n private closeWithoutConfirmationGrants = new Set<object>();\n /**\n * The open state a `setOpen` call is currently applying. Handlers run before\n * `isOpen` is assigned, so a handler that calls `setOpen` again would not be\n * caught by the equality check at the top and would recurse.\n */\n private openStateInFlight: boolean | undefined;\n\n public constructor(options: ConstructorOptions = {}) {\n makeObservable(this, {\n isOpen: observable,\n isContentSuspended: observable,\n showConfirmationModal: observable,\n confirmOnCloseEnabled: computed,\n open: action.bound,\n close: action.bound,\n toggle: action.bound,\n setOpen: action.bound,\n syncOpen: action.bound,\n setIsContentSuspended: action.bound,\n confirmClose: action.bound,\n cancelConfirmation: action.bound,\n });\n const { isDefaultOpen = false, confirmOnClose = false } = options;\n this.isOpen = isDefaultOpen;\n this.confirmOnCloseFromOptions = confirmOnClose;\n }\n\n /** Whether closing this overlay currently requires a confirmation. */\n public get confirmOnCloseEnabled(): boolean {\n if (this.confirmOnCloseSources.size === 0) {\n return this.confirmOnCloseFromOptions;\n }\n return Array.from(this.confirmOnCloseSources.values()).some(Boolean);\n }\n\n public useUpdateOptions(options: OverlayControllerOptions = {}): void {\n const { onOpen, onClose, onOpenChange, confirmOnClose } = options;\n\n this.useOnHandler(onOpen, (h) =>\n this.addOpenStateHandler(h, this.onOpenHandlers),\n );\n this.useOnHandler(onClose, (h) =>\n this.addOpenStateHandler(h, this.onCloseHandlers),\n );\n this.useOnHandler(onOpenChange, (h) =>\n this.addOpenStateHandler(h, this.onOpenChangeHandlers),\n );\n\n this.useConfirmOnCloseSource(confirmOnClose);\n }\n\n /**\n * Registers the calling component as a close confirmation source for as long\n * as it is mounted. `undefined` contributes nothing at all.\n */\n private useConfirmOnCloseSource(confirmOnClose: boolean | undefined): void {\n const source = useStatic((): object => ({}));\n\n useEffect(() => {\n if (confirmOnClose === undefined) {\n this.removeConfirmOnCloseSource(source);\n return;\n }\n this.setConfirmOnCloseSource(source, confirmOnClose);\n return () => this.removeConfirmOnCloseSource(source);\n }, [this, source, confirmOnClose]);\n }\n\n private setConfirmOnCloseSource(source: object, confirmOnClose: boolean) {\n runInAction(() => this.confirmOnCloseSources.set(source, confirmOnClose));\n }\n\n private removeConfirmOnCloseSource(source: object) {\n runInAction(() => this.confirmOnCloseSources.delete(source));\n }\n\n /**\n * Can be used to execute a callback when the Overlay has unmounted – means\n * after any closing animation.\n *\n * NOTICE: This hook only works inside the corresponding Overlay!\n */\n public useOnClosed(\n callback: () => unknown,\n options: OnOverlayClosedOptions = {},\n ) {\n const { dependencies = [] } = options;\n const isOpen = this.useIsOpen();\n const wasOpen = useRef(this.isOpen);\n\n this.useUpdateOptions({\n onOpen: () => {\n wasOpen.current = true;\n },\n });\n\n useEffect(() => {\n return () => {\n if (!isOpen && wasOpen.current) {\n wasOpen.current = false;\n callback();\n }\n };\n }, [isOpen, ...dependencies]);\n }\n\n public static useNew(\n options: OverlayControllerOptions = {},\n ): OverlayController {\n const controller = useStatic(() => new OverlayController(options));\n controller.useUpdateOptions(options);\n return controller;\n }\n\n private addOpenStateHandler<T extends AnyOverlayOpenStateHandler>(\n handler: T,\n handlersSet: Set<T>,\n ): DisposerFn {\n handlersSet.add(handler);\n return () => {\n handlersSet.delete(handler);\n };\n }\n\n private useOnHandler<T extends AnyOverlayOpenStateHandler>(\n handler: T | undefined,\n addHandlerFn: (handler: T) => DisposerFn,\n ) {\n useEffect(\n () => (handler ? addHandlerFn(handler) : undefined),\n [handler, this],\n );\n }\n\n private executeHandlers(\n isOpen: boolean,\n handlers: Set<\n OverlayOpenHandler | OverlayCloseHandler | OverlayOpenStateHandler\n >,\n ): boolean {\n const handlerResult = Array.from(handlers).map((handler) =>\n handler(isOpen),\n );\n return handlerResult.some((result) => result === false);\n }\n\n private executeOnClose(): boolean {\n return this.executeHandlers(false, this.onCloseHandlers);\n }\n\n private executeOnOpen(): boolean {\n return this.executeHandlers(true, this.onOpenHandlers);\n }\n\n private executeOnOpenChange(isOpen: boolean): boolean {\n return this.executeHandlers(isOpen, this.onOpenChangeHandlers);\n }\n\n public addOnClose(handler: OverlayCloseHandler) {\n return this.addOpenStateHandler(handler, this.onCloseHandlers);\n }\n\n public addOnOpen(handler: OverlayOpenHandler) {\n return this.addOpenStateHandler(handler, this.onOpenHandlers);\n }\n\n public addOnOpenChange(handler: OverlayOpenStateHandler) {\n return this.addOpenStateHandler(handler, this.onOpenChangeHandlers);\n }\n\n public open(): void {\n this.setOpen(true);\n }\n\n public close(options?: CloseOptions): void {\n this.setOpen(false, options);\n }\n\n public toggle(): void {\n this.setOpen(!this.isOpen);\n }\n\n public setOpen(toOpen: boolean, options: CloseOptions = {}): void {\n if (this.isOpen === toOpen || this.openStateInFlight === toOpen) {\n return;\n }\n\n this.openStateInFlight = toOpen;\n try {\n this.applyOpenState(toOpen, options);\n } finally {\n this.openStateInFlight = undefined;\n }\n }\n\n /**\n * Sets the open state without running any handler. Use it to mirror a\n * controlled `isOpen` prop, whose change comes from the consumer – notifying\n * them of their own state would echo it back.\n */\n public syncOpen(toOpen: boolean): void {\n this.isOpen = toOpen;\n }\n\n private applyOpenState(toOpen: boolean, options: CloseOptions): void {\n const { bypassConfirmation = false } = options;\n\n if (toOpen === false) {\n // The confirmation is a one-shot permission for exactly this close\n // attempt – consume it here instead of only when a close succeeds, so it\n // cannot survive an aborted close and disarm later attempts.\n const closeIsConfirmed = this.closeIsConfirmed;\n this.closeIsConfirmed = false;\n\n if (\n this.confirmOnCloseEnabled &&\n !closeIsConfirmed &&\n this.closeWithoutConfirmationGrants.size === 0 &&\n !bypassConfirmation\n ) {\n this.showConfirmationModal = true;\n return;\n }\n }\n\n let aborted: boolean;\n if (toOpen) {\n aborted = this.executeOnOpen();\n } else {\n aborted = this.executeOnClose();\n }\n if (!aborted) {\n aborted = this.executeOnOpenChange(toOpen);\n }\n\n if (!aborted) {\n this.isOpen = toOpen;\n }\n }\n\n public setIsContentSuspended(to: boolean): void {\n this.isContentSuspended = to;\n }\n\n public useIsOpen() {\n return useSelector(() => this.isOpen);\n }\n\n public useIsContentSuspended() {\n return useSelector(() => this.isContentSuspended);\n }\n\n public useShowConfirmationModal() {\n return useSelector(() => this.showConfirmationModal);\n }\n\n public useConfirmOnCloseEnabled() {\n return useSelector(() => this.confirmOnCloseEnabled);\n }\n\n public useConfirmationController() {\n return useCloseOverlayConfirmationController(this);\n }\n\n /**\n * Allows closing this overlay without a confirmation until the returned\n * disposer is called. Use it around an operation that may legitimately close\n * the overlay – e.g. a `Form` submit, where a \"discard unsaved changes?\"\n * prompt would be nonsense – and dispose it as soon as that operation has\n * finished. An operation that ends without closing the overlay therefore\n * leaves the confirmation armed (#2775).\n */\n public grantCloseWithoutConfirmation(): DisposerFn {\n const grant = {};\n this.closeWithoutConfirmationGrants.add(grant);\n return () => {\n this.closeWithoutConfirmationGrants.delete(grant);\n };\n }\n\n public confirmClose(): void {\n this.closeIsConfirmed = true;\n this.showConfirmationModal = false;\n }\n\n public cancelConfirmation(): void {\n this.showConfirmationModal = false;\n }\n}\n"],"mappings":"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"}
|
|
@@ -14,6 +14,25 @@ export interface PopoverProps extends PropsWithChildren<Omit<Aria.PopoverProps,
|
|
|
14
14
|
controller?: OverlayController;
|
|
15
15
|
/** A fixed width for the popover. */
|
|
16
16
|
width?: string | number;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the popover is open. Use it to control the popover state – then
|
|
19
|
+
* `onOpenChange` must update the state this value comes from.
|
|
20
|
+
*/
|
|
21
|
+
isOpen?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the popover is open initially. Use it for an uncontrolled popover.
|
|
24
|
+
*
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
isDefaultOpen?: boolean;
|
|
28
|
+
/** @deprecated Use `isDefaultOpen` instead. */
|
|
29
|
+
defaultOpen?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Called with the new open state whenever the popover is opened or closed –
|
|
32
|
+
* on every path, including a close triggered through the controller. It only
|
|
33
|
+
* reports the change; it never performs or suppresses it.
|
|
34
|
+
*/
|
|
35
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
17
36
|
}
|
|
18
37
|
export declare const Popover: import('react').FunctionComponent<PopoverProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
19
38
|
export default Popover;
|
|
@@ -50,6 +50,12 @@ export declare class OverlayController {
|
|
|
50
50
|
* armed.
|
|
51
51
|
*/
|
|
52
52
|
private closeWithoutConfirmationGrants;
|
|
53
|
+
/**
|
|
54
|
+
* The open state a `setOpen` call is currently applying. Handlers run before
|
|
55
|
+
* `isOpen` is assigned, so a handler that calls `setOpen` again would not be
|
|
56
|
+
* caught by the equality check at the top and would recurse.
|
|
57
|
+
*/
|
|
58
|
+
private openStateInFlight;
|
|
53
59
|
constructor(options?: ConstructorOptions);
|
|
54
60
|
/** Whether closing this overlay currently requires a confirmation. */
|
|
55
61
|
get confirmOnCloseEnabled(): boolean;
|
|
@@ -82,6 +88,13 @@ export declare class OverlayController {
|
|
|
82
88
|
close(options?: CloseOptions): void;
|
|
83
89
|
toggle(): void;
|
|
84
90
|
setOpen(toOpen: boolean, options?: CloseOptions): void;
|
|
91
|
+
/**
|
|
92
|
+
* Sets the open state without running any handler. Use it to mirror a
|
|
93
|
+
* controlled `isOpen` prop, whose change comes from the consumer – notifying
|
|
94
|
+
* them of their own state would echo it back.
|
|
95
|
+
*/
|
|
96
|
+
syncOpen(toOpen: boolean): void;
|
|
97
|
+
private applyOpenState;
|
|
85
98
|
setIsContentSuspended(to: boolean): void;
|
|
86
99
|
useIsOpen(): boolean;
|
|
87
100
|
useIsContentSuspended(): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mittwald/flow-react-components",
|
|
3
|
-
"version": "1.2.0-next.
|
|
3
|
+
"version": "1.2.0-next.59",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A React implementation of Flow, mittwald’s design system",
|
|
6
6
|
"homepage": "https://flow.mittwald.de",
|
|
@@ -83,9 +83,9 @@
|
|
|
83
83
|
"@internationalized/string-compiler": "^3.4.1",
|
|
84
84
|
"@lezer/common": "^1.5.2",
|
|
85
85
|
"@lezer/highlight": "^1.2.3",
|
|
86
|
-
"@mittwald/flow-icons": "^1.2.0-next.
|
|
86
|
+
"@mittwald/flow-icons": "^1.2.0-next.59",
|
|
87
87
|
"@mittwald/password-tools-js": "^3.0.0",
|
|
88
|
-
"@mittwald/react-tunnel": "^1.2.0-next.
|
|
88
|
+
"@mittwald/react-tunnel": "^1.2.0-next.59",
|
|
89
89
|
"@mittwald/react-use-promise": "^4.2.2",
|
|
90
90
|
"@react-aria/form": "^3.2.1",
|
|
91
91
|
"@react-aria/i18n": "^3.13.1",
|
|
@@ -138,12 +138,12 @@
|
|
|
138
138
|
"@internationalized/date": "^3.12.3",
|
|
139
139
|
"@lezer/generator": "^1.8.0",
|
|
140
140
|
"@lezer/lr": "^1.4.10",
|
|
141
|
-
"@mittwald/flow-core": "1.2.0-next.
|
|
142
|
-
"@mittwald/flow-design-tokens": "1.2.0-next.
|
|
143
|
-
"@mittwald/flow-icons-base": "1.2.0-next.
|
|
141
|
+
"@mittwald/flow-core": "1.2.0-next.59",
|
|
142
|
+
"@mittwald/flow-design-tokens": "1.2.0-next.59",
|
|
143
|
+
"@mittwald/flow-icons-base": "1.2.0-next.59",
|
|
144
144
|
"@mittwald/react-use-promise": "^4.2.2",
|
|
145
145
|
"@mittwald/remote-dom-react": "1.2.2-mittwald.10",
|
|
146
|
-
"@mittwald/typescript-config": "1.2.0-next.
|
|
146
|
+
"@mittwald/typescript-config": "1.2.0-next.59",
|
|
147
147
|
"@nx/storybook": "^22.7.6",
|
|
148
148
|
"@storybook/addon-a11y": "^10.5.10",
|
|
149
149
|
"@storybook/addon-actions": "^9.0.8",
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
},
|
|
196
196
|
"peerDependencies": {
|
|
197
197
|
"@internationalized/date": "^3.12.2",
|
|
198
|
-
"@mittwald/flow-icons-pro": "1.2.0-next.
|
|
198
|
+
"@mittwald/flow-icons-pro": "1.2.0-next.59",
|
|
199
199
|
"@mittwald/react-use-promise": "^4.2.2",
|
|
200
200
|
"next": "^16.2.3",
|
|
201
201
|
"react": "^19.2.0",
|
|
@@ -219,5 +219,5 @@
|
|
|
219
219
|
"browserslist": [
|
|
220
220
|
"baseline widely available"
|
|
221
221
|
],
|
|
222
|
-
"gitHead": "
|
|
222
|
+
"gitHead": "b42da89ba96244b3654bc2e39f3c8c1e1ed036b7"
|
|
223
223
|
}
|