@cratis/scene.react 3.7.0 → 4.1.0
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/core/CoreButton.tsx +2 -2
- package/dist/cjs/SceneElementView.js +2 -0
- package/dist/cjs/SceneElementView.js.map +1 -1
- package/dist/cjs/core/CoreButton.js +2 -1
- package/dist/cjs/core/CoreButton.js.map +1 -1
- package/dist/cjs/index.js +18 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interactions/ConfirmHost.js +71 -0
- package/dist/cjs/interactions/ConfirmHost.js.map +1 -0
- package/dist/cjs/interactions/InteractionScope.js +74 -0
- package/dist/cjs/interactions/InteractionScope.js.map +1 -0
- package/dist/cjs/interactions/NotificationHost.js +73 -0
- package/dist/cjs/interactions/NotificationHost.js.map +1 -0
- package/dist/cjs/interactions/UnresolvedActionHost.js +53 -0
- package/dist/cjs/interactions/UnresolvedActionHost.js.map +1 -0
- package/dist/cjs/interactions/createBrowserDispatcher.js +102 -0
- package/dist/cjs/interactions/createBrowserDispatcher.js.map +1 -0
- package/dist/cjs/interactions/useInteractionHosts.js +109 -0
- package/dist/cjs/interactions/useInteractionHosts.js.map +1 -0
- package/dist/cjs/interactions/useInteractions.js +122 -0
- package/dist/cjs/interactions/useInteractions.js.map +1 -0
- package/dist/cjs/renderer/InteractiveComponent.js +26 -0
- package/dist/cjs/renderer/InteractiveComponent.js.map +1 -0
- package/dist/cjs/renderer/createReactRenderer.js +3 -1
- package/dist/cjs/renderer/createReactRenderer.js.map +1 -1
- package/dist/esm/SceneElementView.js +2 -0
- package/dist/esm/SceneElementView.js.map +1 -1
- package/dist/esm/core/CoreButton.d.ts +1 -1
- package/dist/esm/core/CoreButton.d.ts.map +1 -1
- package/dist/esm/core/CoreButton.js +2 -1
- package/dist/esm/core/CoreButton.js.map +1 -1
- package/dist/esm/for_useInteractionHosts/when_a_document_asks_the_user_something.d.ts +2 -0
- package/dist/esm/for_useInteractionHosts/when_a_document_asks_the_user_something.d.ts.map +1 -0
- package/dist/esm/for_useInteractionHosts/when_a_document_asks_the_user_something.js +73 -0
- package/dist/esm/for_useInteractionHosts/when_a_document_asks_the_user_something.js.map +1 -0
- package/dist/esm/for_useInteractionHosts/when_the_host_cannot_do_what_was_asked.d.ts +2 -0
- package/dist/esm/for_useInteractionHosts/when_the_host_cannot_do_what_was_asked.d.ts.map +1 -0
- package/dist/esm/for_useInteractionHosts/when_the_host_cannot_do_what_was_asked.js +43 -0
- package/dist/esm/for_useInteractionHosts/when_the_host_cannot_do_what_was_asked.js.map +1 -0
- package/dist/esm/for_useInteractions/when_clicking_an_element_that_executes_a_command.d.ts +2 -0
- package/dist/esm/for_useInteractions/when_clicking_an_element_that_executes_a_command.d.ts.map +1 -0
- package/dist/esm/for_useInteractions/when_clicking_an_element_that_executes_a_command.js +94 -0
- package/dist/esm/for_useInteractions/when_clicking_an_element_that_executes_a_command.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interactions/ConfirmHost.d.ts +8 -0
- package/dist/esm/interactions/ConfirmHost.d.ts.map +1 -0
- package/dist/esm/interactions/ConfirmHost.js +69 -0
- package/dist/esm/interactions/ConfirmHost.js.map +1 -0
- package/dist/esm/interactions/ConfirmRequest.d.ts +5 -0
- package/dist/esm/interactions/ConfirmRequest.d.ts.map +1 -0
- package/dist/esm/interactions/ConfirmRequest.js +2 -0
- package/dist/esm/interactions/ConfirmRequest.js.map +1 -0
- package/dist/esm/interactions/InteractionScope.d.ts +17 -0
- package/dist/esm/interactions/InteractionScope.d.ts.map +1 -0
- package/dist/esm/interactions/InteractionScope.js +70 -0
- package/dist/esm/interactions/InteractionScope.js.map +1 -0
- package/dist/esm/interactions/NotificationHost.d.ts +7 -0
- package/dist/esm/interactions/NotificationHost.d.ts.map +1 -0
- package/dist/esm/interactions/NotificationHost.js +71 -0
- package/dist/esm/interactions/NotificationHost.js.map +1 -0
- package/dist/esm/interactions/SceneNotification.d.ts +7 -0
- package/dist/esm/interactions/SceneNotification.d.ts.map +1 -0
- package/dist/esm/interactions/SceneNotification.js +2 -0
- package/dist/esm/interactions/SceneNotification.js.map +1 -0
- package/dist/esm/interactions/UnresolvedAction.d.ts +5 -0
- package/dist/esm/interactions/UnresolvedAction.d.ts.map +1 -0
- package/dist/esm/interactions/UnresolvedAction.js +2 -0
- package/dist/esm/interactions/UnresolvedAction.js.map +1 -0
- package/dist/esm/interactions/UnresolvedActionHost.d.ts +6 -0
- package/dist/esm/interactions/UnresolvedActionHost.d.ts.map +1 -0
- package/dist/esm/interactions/UnresolvedActionHost.js +51 -0
- package/dist/esm/interactions/UnresolvedActionHost.js.map +1 -0
- package/dist/esm/interactions/createBrowserDispatcher.d.ts +17 -0
- package/dist/esm/interactions/createBrowserDispatcher.d.ts.map +1 -0
- package/dist/esm/interactions/createBrowserDispatcher.js +100 -0
- package/dist/esm/interactions/createBrowserDispatcher.js.map +1 -0
- package/dist/esm/interactions/index.d.ts +11 -0
- package/dist/esm/interactions/index.d.ts.map +1 -0
- package/dist/esm/interactions/index.js +11 -0
- package/dist/esm/interactions/index.js.map +1 -0
- package/dist/esm/interactions/useInteractionHosts.d.ts +11 -0
- package/dist/esm/interactions/useInteractionHosts.d.ts.map +1 -0
- package/dist/esm/interactions/useInteractionHosts.js +107 -0
- package/dist/esm/interactions/useInteractionHosts.js.map +1 -0
- package/dist/esm/interactions/useInteractions.d.ts +16 -0
- package/dist/esm/interactions/useInteractions.d.ts.map +1 -0
- package/dist/esm/interactions/useInteractions.js +120 -0
- package/dist/esm/interactions/useInteractions.js.map +1 -0
- package/dist/esm/renderer/ComponentRegistry.d.ts +2 -0
- package/dist/esm/renderer/ComponentRegistry.d.ts.map +1 -1
- package/dist/esm/renderer/InteractiveComponent.d.ts +10 -0
- package/dist/esm/renderer/InteractiveComponent.d.ts.map +1 -0
- package/dist/esm/renderer/InteractiveComponent.js +24 -0
- package/dist/esm/renderer/InteractiveComponent.js.map +1 -0
- package/dist/esm/renderer/createReactRenderer.d.ts.map +1 -1
- package/dist/esm/renderer/createReactRenderer.js +3 -1
- package/dist/esm/renderer/createReactRenderer.js.map +1 -1
- package/dist/esm/renderer/index.d.ts +1 -0
- package/dist/esm/renderer/index.d.ts.map +1 -1
- package/dist/esm/renderer/index.js +1 -0
- package/dist/esm/renderer/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/for_useInteractionHosts/when_a_document_asks_the_user_something.tsx +106 -0
- package/for_useInteractionHosts/when_the_host_cannot_do_what_was_asked.tsx +66 -0
- package/for_useInteractions/when_clicking_an_element_that_executes_a_command.tsx +141 -0
- package/index.ts +1 -0
- package/interactions/ConfirmHost.tsx +42 -0
- package/interactions/ConfirmRequest.ts +19 -0
- package/interactions/InteractionScope.tsx +94 -0
- package/interactions/NotificationHost.tsx +48 -0
- package/interactions/SceneNotification.ts +18 -0
- package/interactions/UnresolvedAction.ts +17 -0
- package/interactions/UnresolvedActionHost.tsx +33 -0
- package/interactions/createBrowserDispatcher.ts +160 -0
- package/interactions/index.ts +13 -0
- package/interactions/useInteractionHosts.tsx +101 -0
- package/interactions/useInteractions.ts +116 -0
- package/package.json +5 -5
- package/renderer/ComponentRegistry.ts +11 -0
- package/renderer/InteractiveComponent.tsx +25 -0
- package/renderer/createReactRenderer.tsx +2 -1
- package/renderer/index.ts +1 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var ConfirmHost = require('./ConfirmHost.js');
|
|
6
|
+
var NotificationHost = require('./NotificationHost.js');
|
|
7
|
+
var UnresolvedActionHost = require('./UnresolvedActionHost.js');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Provides the default surfaces for the actions that need somewhere to appear.
|
|
11
|
+
*
|
|
12
|
+
* `notify` and `confirm` are the two actions a document can take that have no backend and no route: they are
|
|
13
|
+
* purely things the user sees. Without a surface, a host either invents one or the action goes to the console,
|
|
14
|
+
* which is why these ship with Scene rather than being every application's first chore.
|
|
15
|
+
*
|
|
16
|
+
* The three are returned as one unit because they are wired the same way and forgetting to render one of them
|
|
17
|
+
* is exactly the failure they exist to prevent:
|
|
18
|
+
*
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const { host, surfaces } = useInteractionHosts();
|
|
21
|
+
* const dispatcher = useMemo(() => createBrowserDispatcher({ ...host, executeCommand, navigate }), [host]);
|
|
22
|
+
*
|
|
23
|
+
* return <InteractionScope dispatcher={dispatcher}>{children}{surfaces}</InteractionScope>;
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* An application with a design system of its own passes its own `notify` after the spread and keeps the rest.
|
|
27
|
+
* @param options The {@link InteractionHostOptions}.
|
|
28
|
+
* @returns The {@link InteractionHosts}.
|
|
29
|
+
*/ function useInteractionHosts(options = {}) {
|
|
30
|
+
const { dismissAfter = 6000 } = options;
|
|
31
|
+
const [notifications, setNotifications] = react.useState([]);
|
|
32
|
+
const [unresolved, setUnresolved] = react.useState([]);
|
|
33
|
+
const [request, setRequest] = react.useState(undefined);
|
|
34
|
+
const nextId = react.useRef(0);
|
|
35
|
+
const dismiss = react.useCallback((id)=>setNotifications((current)=>current.filter((_)=>_.id !== id)), []);
|
|
36
|
+
const notify = react.useCallback((level, message)=>{
|
|
37
|
+
const id = nextId.current++;
|
|
38
|
+
setNotifications((current)=>[
|
|
39
|
+
...current,
|
|
40
|
+
{
|
|
41
|
+
id,
|
|
42
|
+
level,
|
|
43
|
+
message
|
|
44
|
+
}
|
|
45
|
+
]);
|
|
46
|
+
if (dismissAfter > 0) {
|
|
47
|
+
setTimeout(()=>setNotifications((current)=>current.filter((_)=>_.id !== id)), dismissAfter);
|
|
48
|
+
}
|
|
49
|
+
}, [
|
|
50
|
+
dismissAfter
|
|
51
|
+
]);
|
|
52
|
+
const confirm = react.useCallback((message)=>new Promise((resolve)=>{
|
|
53
|
+
setRequest({
|
|
54
|
+
message,
|
|
55
|
+
answer (confirmed) {
|
|
56
|
+
// Cleared before resolving, so the sequence that continues cannot find a question on screen
|
|
57
|
+
// that has already been answered.
|
|
58
|
+
setRequest(undefined);
|
|
59
|
+
resolve(confirmed);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}), []);
|
|
63
|
+
const onUnsupported = react.useCallback((action)=>{
|
|
64
|
+
// Reported once per distinct action. An unsupported action on a hover or an interval would otherwise
|
|
65
|
+
// grow the list until it covered the application it is trying to tell you about.
|
|
66
|
+
setUnresolved((current)=>current.some((_)=>_.action === action) ? current : [
|
|
67
|
+
...current,
|
|
68
|
+
{
|
|
69
|
+
id: nextId.current++,
|
|
70
|
+
action
|
|
71
|
+
}
|
|
72
|
+
]);
|
|
73
|
+
}, []);
|
|
74
|
+
const host = react.useMemo(()=>({
|
|
75
|
+
notify,
|
|
76
|
+
confirm,
|
|
77
|
+
onUnsupported
|
|
78
|
+
}), [
|
|
79
|
+
notify,
|
|
80
|
+
confirm,
|
|
81
|
+
onUnsupported
|
|
82
|
+
]);
|
|
83
|
+
const surfaces = react.useMemo(()=>/*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
84
|
+
children: [
|
|
85
|
+
/*#__PURE__*/ jsxRuntime.jsx(NotificationHost.NotificationHost, {
|
|
86
|
+
notifications: notifications,
|
|
87
|
+
onDismiss: dismiss
|
|
88
|
+
}),
|
|
89
|
+
/*#__PURE__*/ jsxRuntime.jsx(ConfirmHost.ConfirmHost, {
|
|
90
|
+
request: request
|
|
91
|
+
}),
|
|
92
|
+
/*#__PURE__*/ jsxRuntime.jsx(UnresolvedActionHost.UnresolvedActionHost, {
|
|
93
|
+
unresolved: unresolved
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
}), [
|
|
97
|
+
notifications,
|
|
98
|
+
dismiss,
|
|
99
|
+
request,
|
|
100
|
+
unresolved
|
|
101
|
+
]);
|
|
102
|
+
return {
|
|
103
|
+
host,
|
|
104
|
+
surfaces
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
exports.useInteractionHosts = useInteractionHosts;
|
|
109
|
+
//# sourceMappingURL=useInteractionHosts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useInteractionHosts.js","sources":["../../../interactions/useInteractionHosts.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ReactNode, useCallback, useMemo, useRef, useState } from 'react';\nimport { NotificationLevel } from '@cratis/scene.model';\nimport { BrowserDispatcherHost } from './createBrowserDispatcher';\nimport { ConfirmHost } from './ConfirmHost';\nimport { ConfirmRequest } from './ConfirmRequest';\nimport { NotificationHost } from './NotificationHost';\nimport { SceneNotification } from './SceneNotification';\nimport { UnresolvedAction } from './UnresolvedAction';\nimport { UnresolvedActionHost } from './UnresolvedActionHost';\n\n/**\n * What {@link useInteractionHosts} gives an application.\n */\nexport interface InteractionHosts {\n /** The parts of a {@link BrowserDispatcherHost} these surfaces provide. Spread it into the host you build. */\n readonly host: Pick<BrowserDispatcherHost, 'notify' | 'confirm' | 'onUnsupported'>;\n\n /** The surfaces themselves. Render them once, anywhere inside the application. */\n readonly surfaces: ReactNode;\n}\n\n/**\n * How long a notification stays before it removes itself, in milliseconds.\n */\nexport interface InteractionHostOptions {\n /** How long a notification stays up. `0` keeps it until it is dismissed. */\n readonly dismissAfter?: number;\n}\n\n/**\n * Provides the default surfaces for the actions that need somewhere to appear.\n *\n * `notify` and `confirm` are the two actions a document can take that have no backend and no route: they are\n * purely things the user sees. Without a surface, a host either invents one or the action goes to the console,\n * which is why these ship with Scene rather than being every application's first chore.\n *\n * The three are returned as one unit because they are wired the same way and forgetting to render one of them\n * is exactly the failure they exist to prevent:\n *\n * ```tsx\n * const { host, surfaces } = useInteractionHosts();\n * const dispatcher = useMemo(() => createBrowserDispatcher({ ...host, executeCommand, navigate }), [host]);\n *\n * return <InteractionScope dispatcher={dispatcher}>{children}{surfaces}</InteractionScope>;\n * ```\n *\n * An application with a design system of its own passes its own `notify` after the spread and keeps the rest.\n * @param options The {@link InteractionHostOptions}.\n * @returns The {@link InteractionHosts}.\n */\nexport function useInteractionHosts(options: InteractionHostOptions = {}): InteractionHosts {\n const { dismissAfter = 6000 } = options;\n const [notifications, setNotifications] = useState<readonly SceneNotification[]>([]);\n const [unresolved, setUnresolved] = useState<readonly UnresolvedAction[]>([]);\n const [request, setRequest] = useState<ConfirmRequest | undefined>(undefined);\n const nextId = useRef(0);\n\n const dismiss = useCallback((id: number) => setNotifications(current => current.filter(_ => _.id !== id)), []);\n\n const notify = useCallback((level: NotificationLevel, message: string) => {\n const id = nextId.current++;\n setNotifications(current => [...current, { id, level, message }]);\n\n if (dismissAfter > 0) {\n setTimeout(() => setNotifications(current => current.filter(_ => _.id !== id)), dismissAfter);\n }\n }, [dismissAfter]);\n\n const confirm = useCallback((message: string) => new Promise<boolean>(resolve => {\n setRequest({\n message,\n answer(confirmed) {\n // Cleared before resolving, so the sequence that continues cannot find a question on screen\n // that has already been answered.\n setRequest(undefined);\n resolve(confirmed);\n },\n });\n }), []);\n\n const onUnsupported = useCallback((action: string) => {\n // Reported once per distinct action. An unsupported action on a hover or an interval would otherwise\n // grow the list until it covered the application it is trying to tell you about.\n setUnresolved(current => current.some(_ => _.action === action) ? current : [...current, { id: nextId.current++, action }]);\n }, []);\n\n const host = useMemo(() => ({ notify, confirm, onUnsupported }), [notify, confirm, onUnsupported]);\n\n const surfaces = useMemo(() => (\n <>\n <NotificationHost notifications={notifications} onDismiss={dismiss} />\n <ConfirmHost request={request} />\n <UnresolvedActionHost unresolved={unresolved} />\n </>\n ), [notifications, dismiss, request, unresolved]);\n\n return { host, surfaces };\n}\n"],"names":["useInteractionHosts","options","dismissAfter","notifications","setNotifications","useState","unresolved","setUnresolved","request","setRequest","undefined","nextId","useRef","dismiss","useCallback","id","current","filter","_","notify","level","message","setTimeout","confirm","Promise","resolve","answer","confirmed","onUnsupported","action","some","host","useMemo","surfaces","_jsxs","_Fragment","_jsx","NotificationHost","onDismiss","ConfirmHost","UnresolvedActionHost"],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var scene_engine = require('@cratis/scene.engine');
|
|
5
|
+
var scene_model = require('@cratis/scene.model');
|
|
6
|
+
var InteractionScope = require('./InteractionScope.js');
|
|
7
|
+
|
|
8
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
9
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
10
|
+
const domTriggers = [
|
|
11
|
+
[
|
|
12
|
+
'onClick',
|
|
13
|
+
scene_model.InteractionTriggerKind.Click
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
'onDoubleClick',
|
|
17
|
+
scene_model.InteractionTriggerKind.DoubleClick
|
|
18
|
+
],
|
|
19
|
+
[
|
|
20
|
+
'onChange',
|
|
21
|
+
scene_model.InteractionTriggerKind.Change
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
'onSubmit',
|
|
25
|
+
scene_model.InteractionTriggerKind.Submit
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
'onSelect',
|
|
29
|
+
scene_model.InteractionTriggerKind.Select
|
|
30
|
+
]
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Wires the behaviors attached to an element to the events that start them.
|
|
34
|
+
*
|
|
35
|
+
* The hook resolves and runs; it decides nothing. Which bindings a trigger runs, in what order, and where a
|
|
36
|
+
* sequence stops are all the engine's, so a different renderer inherits the same semantics instead of
|
|
37
|
+
* reimplementing them - and so all of it stays testable without a DOM.
|
|
38
|
+
*
|
|
39
|
+
* Lifecycle triggers are handled here rather than by the caller because React is what knows when an element
|
|
40
|
+
* mounts: `load` and `unload` follow the element, and `enter` and `leave` follow the screen. An `interval`
|
|
41
|
+
* becomes a timer that is cleared on unmount, because a document declaring one is asking for it to run while
|
|
42
|
+
* the thing it is attached to is on screen and not afterwards.
|
|
43
|
+
*
|
|
44
|
+
* @param level What is being attached to, used when reporting a finding.
|
|
45
|
+
* @param behaviors The behaviors attached to this element.
|
|
46
|
+
* @returns The handlers to spread onto the rendered element.
|
|
47
|
+
*/ function useInteractions(level, behaviors) {
|
|
48
|
+
const scope = InteractionScope.useInteractionScope();
|
|
49
|
+
const attachments = react.useMemo(()=>[
|
|
50
|
+
...scope?.attachments ?? [],
|
|
51
|
+
...InteractionScope.attachmentFor(level, behaviors)
|
|
52
|
+
], [
|
|
53
|
+
scope,
|
|
54
|
+
level,
|
|
55
|
+
behaviors
|
|
56
|
+
]);
|
|
57
|
+
const run = react.useCallback(async (kind)=>{
|
|
58
|
+
if (!scope) return;
|
|
59
|
+
const result = await scene_engine.runBindings(scene_engine.resolveBehaviors(attachments, kind), scope.dispatcher, scope.context);
|
|
60
|
+
if (result.findings.length > 0) scope.onFindings?.(result.findings);
|
|
61
|
+
}, [
|
|
62
|
+
scope,
|
|
63
|
+
attachments
|
|
64
|
+
]);
|
|
65
|
+
// Mount and unmount, for the element and for the screen it is on. Running the unload and leave bindings
|
|
66
|
+
// from the cleanup is what makes them reliable: React guarantees it, a component remembering to call
|
|
67
|
+
// something on the way out does not.
|
|
68
|
+
react.useEffect(()=>{
|
|
69
|
+
void run(scene_model.InteractionTriggerKind.Load);
|
|
70
|
+
void run(scene_model.InteractionTriggerKind.Enter);
|
|
71
|
+
return ()=>{
|
|
72
|
+
void run(scene_model.InteractionTriggerKind.Unload);
|
|
73
|
+
void run(scene_model.InteractionTriggerKind.Leave);
|
|
74
|
+
};
|
|
75
|
+
}, [
|
|
76
|
+
run
|
|
77
|
+
]);
|
|
78
|
+
react.useEffect(()=>{
|
|
79
|
+
const intervals = scene_engine.resolveIntervalBehaviors(attachments);
|
|
80
|
+
if (intervals.length === 0 || !scope) return;
|
|
81
|
+
const timers = intervals.map((candidate)=>{
|
|
82
|
+
const seconds = candidate.binding.trigger.seconds ?? 0;
|
|
83
|
+
return setInterval(()=>void scene_engine.runBindings([
|
|
84
|
+
candidate
|
|
85
|
+
], scope.dispatcher, scope.context), Math.max(seconds, 1) * 1000);
|
|
86
|
+
});
|
|
87
|
+
return ()=>timers.forEach(clearInterval);
|
|
88
|
+
}, [
|
|
89
|
+
attachments,
|
|
90
|
+
scope
|
|
91
|
+
]);
|
|
92
|
+
return react.useMemo(()=>{
|
|
93
|
+
const handlers = {};
|
|
94
|
+
if (!scope) return handlers;
|
|
95
|
+
for (const [handler, kind] of domTriggers){
|
|
96
|
+
// Nothing attached for this trigger means no handler at all. Attaching an empty one would make
|
|
97
|
+
// every element look interactive and would stop clicks reaching whatever encloses it.
|
|
98
|
+
if (scene_engine.resolveBehaviors(attachments, kind).length === 0) continue;
|
|
99
|
+
if (handler === 'onSubmit') {
|
|
100
|
+
handlers.onSubmit = (event)=>{
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
void run(kind);
|
|
103
|
+
};
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
handlers[handler] = (event)=>{
|
|
107
|
+
// An interaction that ran here is handled here. Without this, a click on a row inside a table
|
|
108
|
+
// that both declare `on click` would run the table's twice: once for the row, once on the way up.
|
|
109
|
+
event?.stopPropagation();
|
|
110
|
+
void run(kind);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return handlers;
|
|
114
|
+
}, [
|
|
115
|
+
scope,
|
|
116
|
+
attachments,
|
|
117
|
+
run
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
exports.useInteractions = useInteractions;
|
|
122
|
+
//# sourceMappingURL=useInteractions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useInteractions.js","sources":["../../../interactions/useInteractions.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useCallback, useEffect, useMemo } from 'react';\nimport { BehaviorAttachment, resolveBehaviors, resolveIntervalBehaviors, runBindings } from '@cratis/scene.engine';\nimport { Behavior, InteractionTriggerKind } from '@cratis/scene.model';\nimport { attachmentFor, useInteractionScope } from './InteractionScope';\n\n/**\n * The DOM handlers an element should spread onto whatever it renders.\n *\n * Only the handlers a document actually asks for are present. An element with no `on click` gets no `onClick`,\n * so it does not become clickable - and, just as importantly, does not swallow the click from something\n * enclosing it that does.\n */\nexport interface InteractionHandlers {\n onClick?: (event: { stopPropagation(): void }) => void;\n onDoubleClick?: (event: { stopPropagation(): void }) => void;\n onChange?: () => void;\n onSubmit?: (event: { preventDefault(): void }) => void;\n onSelect?: () => void;\n}\n\nconst domTriggers: [keyof InteractionHandlers, InteractionTriggerKind][] = [\n ['onClick', InteractionTriggerKind.Click],\n ['onDoubleClick', InteractionTriggerKind.DoubleClick],\n ['onChange', InteractionTriggerKind.Change],\n ['onSubmit', InteractionTriggerKind.Submit],\n ['onSelect', InteractionTriggerKind.Select],\n];\n\n/**\n * Wires the behaviors attached to an element to the events that start them.\n *\n * The hook resolves and runs; it decides nothing. Which bindings a trigger runs, in what order, and where a\n * sequence stops are all the engine's, so a different renderer inherits the same semantics instead of\n * reimplementing them - and so all of it stays testable without a DOM.\n *\n * Lifecycle triggers are handled here rather than by the caller because React is what knows when an element\n * mounts: `load` and `unload` follow the element, and `enter` and `leave` follow the screen. An `interval`\n * becomes a timer that is cleared on unmount, because a document declaring one is asking for it to run while\n * the thing it is attached to is on screen and not afterwards.\n *\n * @param level What is being attached to, used when reporting a finding.\n * @param behaviors The behaviors attached to this element.\n * @returns The handlers to spread onto the rendered element.\n */\nexport function useInteractions(level: string, behaviors?: Behavior[]): InteractionHandlers {\n const scope = useInteractionScope();\n\n const attachments = useMemo<BehaviorAttachment[]>(\n () => [...(scope?.attachments ?? []), ...attachmentFor(level, behaviors)],\n [scope, level, behaviors]);\n\n const run = useCallback(async (kind: InteractionTriggerKind) => {\n if (!scope) return;\n\n const result = await runBindings(resolveBehaviors(attachments, kind), scope.dispatcher, scope.context);\n if (result.findings.length > 0) scope.onFindings?.(result.findings);\n }, [scope, attachments]);\n\n // Mount and unmount, for the element and for the screen it is on. Running the unload and leave bindings\n // from the cleanup is what makes them reliable: React guarantees it, a component remembering to call\n // something on the way out does not.\n useEffect(() => {\n void run(InteractionTriggerKind.Load);\n void run(InteractionTriggerKind.Enter);\n\n return () => {\n void run(InteractionTriggerKind.Unload);\n void run(InteractionTriggerKind.Leave);\n };\n }, [run]);\n\n useEffect(() => {\n const intervals = resolveIntervalBehaviors(attachments);\n if (intervals.length === 0 || !scope) return;\n\n const timers = intervals.map(candidate => {\n const seconds = (candidate.binding.trigger as { seconds?: number }).seconds ?? 0;\n return setInterval(\n () => void runBindings([candidate], scope.dispatcher, scope.context),\n Math.max(seconds, 1) * 1000);\n });\n\n return () => timers.forEach(clearInterval);\n }, [attachments, scope]);\n\n return useMemo(() => {\n const handlers: InteractionHandlers = {};\n if (!scope) return handlers;\n\n for (const [handler, kind] of domTriggers) {\n // Nothing attached for this trigger means no handler at all. Attaching an empty one would make\n // every element look interactive and would stop clicks reaching whatever encloses it.\n if (resolveBehaviors(attachments, kind).length === 0) continue;\n\n if (handler === 'onSubmit') {\n handlers.onSubmit = event => {\n event.preventDefault();\n void run(kind);\n };\n continue;\n }\n\n handlers[handler] = ((event?: { stopPropagation(): void }) => {\n // An interaction that ran here is handled here. Without this, a click on a row inside a table\n // that both declare `on click` would run the table's twice: once for the row, once on the way up.\n event?.stopPropagation();\n void run(kind);\n }) as never;\n }\n\n return handlers;\n }, [scope, attachments, run]);\n}\n"],"names":["domTriggers","InteractionTriggerKind","Click","DoubleClick","Change","Submit","Select","useInteractions","level","behaviors","scope","useInteractionScope","attachments","useMemo","attachmentFor","run","useCallback","kind","result","runBindings","resolveBehaviors","dispatcher","context","findings","length","onFindings","useEffect","Load","Enter","Unload","Leave","intervals","resolveIntervalBehaviors","timers","map","candidate","seconds","binding","trigger","setInterval","Math","max","forEach","clearInterval","handlers","handler","onSubmit","event","preventDefault","stopPropagation"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
require('react/jsx-runtime');
|
|
5
|
+
var useInteractions = require('../interactions/useInteractions.js');
|
|
6
|
+
require('../interactions/NotificationHost.js');
|
|
7
|
+
|
|
8
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
9
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
10
|
+
/**
|
|
11
|
+
* Resolves an element's interactions and hands them to the component that renders it.
|
|
12
|
+
*
|
|
13
|
+
* This exists as a component rather than as a call inside the renderer because resolving them is a hook, and a
|
|
14
|
+
* hook cannot run inside the plain function a {@link Renderer} is. Keeping the renderer plain is what lets the
|
|
15
|
+
* same element tree be rendered by something that is not React at all.
|
|
16
|
+
*/ function InteractiveComponent({ component, element, slots }) {
|
|
17
|
+
const interactions = useInteractions.useInteractions(element.id, element.behaviors);
|
|
18
|
+
return /*#__PURE__*/ react.createElement(component, {
|
|
19
|
+
element,
|
|
20
|
+
slots,
|
|
21
|
+
interactions
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
exports.InteractiveComponent = InteractiveComponent;
|
|
26
|
+
//# sourceMappingURL=InteractiveComponent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InteractiveComponent.js","sources":["../../../renderer/InteractiveComponent.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ComponentType, ReactNode, createElement } from 'react';\nimport { ExternalComponent } from '@cratis/scene.model';\nimport { useInteractions } from '../interactions';\nimport { RegisteredComponentProps } from './ComponentRegistry';\n\nexport interface InteractiveComponentProps {\n component: ComponentType<RegisteredComponentProps>;\n element: ExternalComponent;\n slots: Record<string, ReactNode[]>;\n}\n\n/**\n * Resolves an element's interactions and hands them to the component that renders it.\n *\n * This exists as a component rather than as a call inside the renderer because resolving them is a hook, and a\n * hook cannot run inside the plain function a {@link Renderer} is. Keeping the renderer plain is what lets the\n * same element tree be rendered by something that is not React at all.\n */\nexport function InteractiveComponent({ component, element, slots }: InteractiveComponentProps) {\n const interactions = useInteractions(element.id, element.behaviors);\n return createElement(component, { element, slots, interactions });\n}\n"],"names":["InteractiveComponent","component","element","slots","interactions","useInteractions","id","behaviors","createElement"],"mappings":";;;;;;;AAAA;AACA;AAaA;;;;;;IAOO,SAASA,oBAAAA,CAAqB,EAAEC,SAAS,EAAEC,OAAO,EAAEC,KAAK,EAA6B,EAAA;AACzF,IAAA,MAAMC,eAAeC,+BAAAA,CAAgBH,OAAAA,CAAQI,EAAE,EAAEJ,QAAQK,SAAS,CAAA;AAClE,IAAA,qBAAOC,oBAAcP,SAAAA,EAAW;AAAEC,QAAAA,OAAAA;AAASC,QAAAA,KAAAA;AAAOC,QAAAA;AAAa,KAAA,CAAA;AACnE;;"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
|
+
var InteractiveComponent = require('./InteractiveComponent.js');
|
|
4
5
|
var panelLayout = require('./panelLayout.js');
|
|
5
6
|
var UnresolvedComponent = require('./UnresolvedComponent.js');
|
|
6
7
|
|
|
@@ -31,8 +32,9 @@ var UnresolvedComponent = require('./UnresolvedComponent.js');
|
|
|
31
32
|
},
|
|
32
33
|
renderExternalComponent (element, slots) {
|
|
33
34
|
const Component = registry[element.componentName] ?? UnresolvedComponent.UnresolvedComponent;
|
|
34
|
-
return /*#__PURE__*/ react.createElement(
|
|
35
|
+
return /*#__PURE__*/ react.createElement(InteractiveComponent.InteractiveComponent, {
|
|
35
36
|
key: element.id,
|
|
37
|
+
component: Component,
|
|
36
38
|
element,
|
|
37
39
|
slots
|
|
38
40
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createReactRenderer.js","sources":["../../../renderer/createReactRenderer.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ReactNode, createElement } from 'react';\nimport { Renderer } from '@cratis/scene.engine';\nimport { ContentControl, ExternalComponent, ItemsControl, Panel } from '@cratis/scene.model';\nimport { ComponentRegistry } from './ComponentRegistry';\nimport { childStyle, panelStyle } from './panelLayout';\nimport { UnresolvedComponent } from './UnresolvedComponent';\n\n/**\n * Creates a {@link Renderer} that turns a Scene element tree into React elements.\n *\n * `ItemsControl` and `ContentControl` render as plain wrapping `div`s. A `Panel` renders as the\n * arrangement it declares - a grid, a stack, a wrap or a dock - because that is what those panels are\n * for; see `panelLayout` for the mapping. Arranging a *layout's slots* by `flow`/`freeform` is a\n * different thing and stays Scene#4's, layered on top of this renderer rather than folded into it.\n */\nexport function createReactRenderer(registry: ComponentRegistry): Renderer<ReactNode> {\n return {\n renderContentControl(element: ContentControl, content: ReactNode): ReactNode {\n return createElement('div', { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'ContentControl' }, content);\n },\n\n renderItemsControl(element: ItemsControl, items: ReactNode[]): ReactNode {\n return createElement('div', { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'ItemsControl' }, items);\n },\n\n renderExternalComponent(element: ExternalComponent, slots: Record<string, ReactNode[]>): ReactNode {\n const Component = registry[element.componentName] ?? UnresolvedComponent;\n return createElement(
|
|
1
|
+
{"version":3,"file":"createReactRenderer.js","sources":["../../../renderer/createReactRenderer.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ReactNode, createElement } from 'react';\nimport { Renderer } from '@cratis/scene.engine';\nimport { ContentControl, ExternalComponent, ItemsControl, Panel } from '@cratis/scene.model';\nimport { ComponentRegistry } from './ComponentRegistry';\nimport { InteractiveComponent } from './InteractiveComponent';\nimport { childStyle, panelStyle } from './panelLayout';\nimport { UnresolvedComponent } from './UnresolvedComponent';\n\n/**\n * Creates a {@link Renderer} that turns a Scene element tree into React elements.\n *\n * `ItemsControl` and `ContentControl` render as plain wrapping `div`s. A `Panel` renders as the\n * arrangement it declares - a grid, a stack, a wrap or a dock - because that is what those panels are\n * for; see `panelLayout` for the mapping. Arranging a *layout's slots* by `flow`/`freeform` is a\n * different thing and stays Scene#4's, layered on top of this renderer rather than folded into it.\n */\nexport function createReactRenderer(registry: ComponentRegistry): Renderer<ReactNode> {\n return {\n renderContentControl(element: ContentControl, content: ReactNode): ReactNode {\n return createElement('div', { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'ContentControl' }, content);\n },\n\n renderItemsControl(element: ItemsControl, items: ReactNode[]): ReactNode {\n return createElement('div', { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'ItemsControl' }, items);\n },\n\n renderExternalComponent(element: ExternalComponent, slots: Record<string, ReactNode[]>): ReactNode {\n const Component = registry[element.componentName] ?? UnresolvedComponent;\n return createElement(InteractiveComponent, { key: element.id, component: Component, element, slots });\n },\n\n renderPanel(element: Panel, children: ReactNode[]): ReactNode {\n // The rendered children arrive in the same order as `element.children`, so a panel that places\n // its children individually - a grid cell, a docked edge - can wrap each one in the box that\n // places it without needing to re-render anything.\n const placed = children.map((child, index) => {\n const style = childStyle(element, element.children[index], index);\n return style\n ? createElement('div', { key: `${element.id}-${index}`, 'data-scene-placement': index, style }, child)\n : child;\n });\n\n return createElement(\n 'div',\n { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'Panel', style: panelStyle(element) },\n placed);\n },\n };\n}\n"],"names":["createReactRenderer","registry","renderContentControl","element","content","createElement","key","id","renderItemsControl","items","renderExternalComponent","slots","Component","componentName","UnresolvedComponent","InteractiveComponent","component","renderPanel","children","placed","map","child","index","style","childStyle","panelStyle"],"mappings":";;;;;;;AAAA;AACA;AAUA;;;;;;;IAQO,SAASA,mBAAAA,CAAoBC,QAA2B,EAAA;IAC3D,OAAO;QACHC,oBAAAA,CAAAA,CAAqBC,OAAuB,EAAEC,OAAkB,EAAA;AAC5D,YAAA,qBAAOC,oBAAc,KAAA,EAAO;AAAEC,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAE,gBAAA,eAAA,EAAiBJ,QAAQI,EAAE;gBAAE,iBAAA,EAAmB;aAAiB,EAAGH,OAAAA,CAAAA;AACvH,QAAA,CAAA;QAEAI,kBAAAA,CAAAA,CAAmBL,OAAqB,EAAEM,KAAkB,EAAA;AACxD,YAAA,qBAAOJ,oBAAc,KAAA,EAAO;AAAEC,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAE,gBAAA,eAAA,EAAiBJ,QAAQI,EAAE;gBAAE,iBAAA,EAAmB;aAAe,EAAGE,KAAAA,CAAAA;AACrH,QAAA,CAAA;QAEAC,uBAAAA,CAAAA,CAAwBP,OAA0B,EAAEQ,KAAkC,EAAA;AAClF,YAAA,MAAMC,YAAYX,QAAQ,CAACE,OAAAA,CAAQU,aAAa,CAAC,IAAIC,uCAAAA;AACrD,YAAA,qBAAOT,oBAAcU,yCAAAA,EAAsB;AAAET,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;gBAAES,SAAAA,EAAWJ,SAAAA;AAAWT,gBAAAA,OAAAA;AAASQ,gBAAAA;AAAM,aAAA,CAAA;AACvG,QAAA,CAAA;QAEAM,WAAAA,CAAAA,CAAYd,OAAc,EAAEe,QAAqB,EAAA;;;;AAI7C,YAAA,MAAMC,MAAAA,GAASD,QAAAA,CAASE,GAAG,CAAC,CAACC,KAAAA,EAAOC,KAAAA,GAAAA;AAChC,gBAAA,MAAMC,QAAQC,sBAAAA,CAAWrB,OAAAA,EAASA,QAAQe,QAAQ,CAACI,MAAM,EAAEA,KAAAA,CAAAA;gBAC3D,OAAOC,KAAAA,iBACDlB,oBAAc,KAAA,EAAO;AAAEC,oBAAAA,GAAAA,EAAK,GAAGH,OAAAA,CAAQI,EAAE,CAAC,CAAC,EAAEe,KAAAA,CAAAA,CAAO;oBAAE,sBAAA,EAAwBA,KAAAA;AAAOC,oBAAAA;AAAM,iBAAA,EAAGF,KAAAA,CAAAA,GAC9FA,KAAAA;AACV,YAAA,CAAA,CAAA;AAEA,YAAA,qBAAOhB,oBACH,KAAA,EACA;AAAEC,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAE,gBAAA,eAAA,EAAiBJ,QAAQI,EAAE;gBAAE,iBAAA,EAAmB,OAAA;AAASgB,gBAAAA,KAAAA,EAAOE,sBAAAA,CAAWtB,OAAAA;aAAS,EACvGgB,MAAAA,CAAAA;AACR,QAAA;AACJ,KAAA;AACJ;;"}
|
|
@@ -2,6 +2,8 @@ import { jsx, Fragment } from 'react/jsx-runtime';
|
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { renderElement } from '@cratis/scene.engine';
|
|
4
4
|
import { createReactRenderer } from './renderer/createReactRenderer.js';
|
|
5
|
+
import './interactions/useInteractions.js';
|
|
6
|
+
import './interactions/NotificationHost.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Renders a Scene element tree with the real `Scene.React` renderer - the WYSIWYG building block Studio's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SceneElementView.js","sources":["../../SceneElementView.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useMemo } from 'react';\nimport { renderElement, BindingResolver } from '@cratis/scene.engine';\nimport { SceneElement } from '@cratis/scene.model';\nimport { ComponentRegistry, createReactRenderer } from './renderer';\n\nexport interface SceneElementViewProps {\n element: SceneElement;\n registry: ComponentRegistry;\n resolveBinding: BindingResolver;\n}\n\n/**\n * Renders a Scene element tree with the real `Scene.React` renderer - the WYSIWYG building block Studio's\n * preview surface and Stage's shipped web bundle both consume unmodified.\n */\nexport function SceneElementView({ element, registry, resolveBinding }: SceneElementViewProps) {\n const renderer = useMemo(() => createReactRenderer(registry), [registry]);\n return <>{renderElement(element, renderer, resolveBinding)}</>;\n}\n"],"names":["SceneElementView","element","registry","resolveBinding","renderer","useMemo","createReactRenderer","_jsx","_Fragment","renderElement"],"mappings":"
|
|
1
|
+
{"version":3,"file":"SceneElementView.js","sources":["../../SceneElementView.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useMemo } from 'react';\nimport { renderElement, BindingResolver } from '@cratis/scene.engine';\nimport { SceneElement } from '@cratis/scene.model';\nimport { ComponentRegistry, createReactRenderer } from './renderer';\n\nexport interface SceneElementViewProps {\n element: SceneElement;\n registry: ComponentRegistry;\n resolveBinding: BindingResolver;\n}\n\n/**\n * Renders a Scene element tree with the real `Scene.React` renderer - the WYSIWYG building block Studio's\n * preview surface and Stage's shipped web bundle both consume unmodified.\n */\nexport function SceneElementView({ element, registry, resolveBinding }: SceneElementViewProps) {\n const renderer = useMemo(() => createReactRenderer(registry), [registry]);\n return <>{renderElement(element, renderer, resolveBinding)}</>;\n}\n"],"names":["SceneElementView","element","registry","resolveBinding","renderer","useMemo","createReactRenderer","_jsx","_Fragment","renderElement"],"mappings":";;;;;;;AAcA;;;IAIO,SAASA,gBAAAA,CAAiB,EAAEC,OAAO,EAAEC,QAAQ,EAAEC,cAAc,EAAyB,EAAA;AACzF,IAAA,MAAMC,QAAAA,GAAWC,OAAAA,CAAQ,IAAMC,mBAAAA,CAAoBJ,QAAAA,CAAAA,EAAW;AAACA,QAAAA;AAAS,KAAA,CAAA;IACxE,qBAAOK,GAAA,CAAAC,QAAA,EAAA;AAAGC,QAAAA,QAAAA,EAAAA,aAAAA,CAAcR,SAASG,QAAAA,EAAUD,cAAAA;;AAC/C;;"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { RegisteredComponentProps } from '../renderer';
|
|
2
|
-
export declare function CoreButton({ element, slots }: RegisteredComponentProps): import("react").JSX.Element;
|
|
2
|
+
export declare function CoreButton({ element, slots, interactions }: RegisteredComponentProps): import("react").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=CoreButton.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoreButton.d.ts","sourceRoot":"","sources":["../../../core/CoreButton.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAKvD,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,wBAAwB,+
|
|
1
|
+
{"version":3,"file":"CoreButton.d.ts","sourceRoot":"","sources":["../../../core/CoreButton.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAKvD,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,wBAAwB,+BAQpF"}
|
|
@@ -4,11 +4,12 @@ import { jsxs } from 'react/jsx-runtime';
|
|
|
4
4
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
5
5
|
/**
|
|
6
6
|
* The `core:button` component - part of the minimum vocabulary a `ui profile` can always fall back to.
|
|
7
|
-
*/ function CoreButton({ element, slots }) {
|
|
7
|
+
*/ function CoreButton({ element, slots, interactions }) {
|
|
8
8
|
const label = typeof element.properties.label === 'string' ? element.properties.label : '';
|
|
9
9
|
return /*#__PURE__*/ jsxs("button", {
|
|
10
10
|
type: "button",
|
|
11
11
|
"data-scene-id": element.id,
|
|
12
|
+
...interactions,
|
|
12
13
|
children: [
|
|
13
14
|
label,
|
|
14
15
|
slots.content
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoreButton.js","sources":["../../../core/CoreButton.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { RegisteredComponentProps } from '../renderer';\n\n/**\n * The `core:button` component - part of the minimum vocabulary a `ui profile` can always fall back to.\n */\nexport function CoreButton({ element, slots }: RegisteredComponentProps) {\n const label = typeof element.properties.label === 'string' ? element.properties.label : '';\n return (\n <button type=\"button\" data-scene-id={element.id}>\n {label}\n {slots.content}\n </button>\n );\n}\n"],"names":["CoreButton","element","slots","label","properties","_jsxs","button","type","data-scene-id","id","content"],"mappings":";;AAAA;AACA;AAIA;;
|
|
1
|
+
{"version":3,"file":"CoreButton.js","sources":["../../../core/CoreButton.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { RegisteredComponentProps } from '../renderer';\n\n/**\n * The `core:button` component - part of the minimum vocabulary a `ui profile` can always fall back to.\n */\nexport function CoreButton({ element, slots, interactions }: RegisteredComponentProps) {\n const label = typeof element.properties.label === 'string' ? element.properties.label : '';\n return (\n <button type=\"button\" data-scene-id={element.id} {...interactions}>\n {label}\n {slots.content}\n </button>\n );\n}\n"],"names":["CoreButton","element","slots","interactions","label","properties","_jsxs","button","type","data-scene-id","id","content"],"mappings":";;AAAA;AACA;AAIA;;IAGO,SAASA,UAAAA,CAAW,EAAEC,OAAO,EAAEC,KAAK,EAAEC,YAAY,EAA4B,EAAA;AACjF,IAAA,MAAMC,KAAAA,GAAQ,OAAOH,OAAAA,CAAQI,UAAU,CAACD,KAAK,KAAK,QAAA,GAAWH,OAAAA,CAAQI,UAAU,CAACD,KAAK,GAAG,EAAA;AACxF,IAAA,qBACIE,IAAA,CAACC,QAAAA,EAAAA;QAAOC,IAAAA,EAAK,QAAA;AAASC,QAAAA,eAAAA,EAAeR,QAAQS,EAAE;AAAG,QAAA,GAAGP,YAAY;;AAC5DC,YAAAA,KAAAA;AACAF,YAAAA,KAAAA,CAAMS;;;AAGnB;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when_a_document_asks_the_user_something.d.ts","sourceRoot":"","sources":["../../../for_useInteractionHosts/when_a_document_asks_the_user_something.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { InteractionActionKind, InteractionTriggerKind, NotificationLevel } from '@cratis/scene.model';
|
|
6
|
+
import { InteractionScope, createBrowserDispatcher, useInteractionHosts, useInteractions } from '../interactions';
|
|
7
|
+
const executed = [];
|
|
8
|
+
const confirmThenExecute = {
|
|
9
|
+
name: 'CancelInvoice',
|
|
10
|
+
bindings: [
|
|
11
|
+
{
|
|
12
|
+
trigger: { kind: InteractionTriggerKind.Click },
|
|
13
|
+
actions: [
|
|
14
|
+
{ kind: InteractionActionKind.Confirm, message: { text: 'Cancel this invoice?' } },
|
|
15
|
+
{ kind: InteractionActionKind.ExecuteCommand, command: 'CancelInvoice' },
|
|
16
|
+
{ kind: InteractionActionKind.Notify, level: NotificationLevel.Info, message: { text: 'Invoice cancelled' } },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
function Button({ behaviors }) {
|
|
22
|
+
const handlers = useInteractions('element', behaviors);
|
|
23
|
+
return _jsx("button", { type: "button", ...handlers, children: "Cancel" });
|
|
24
|
+
}
|
|
25
|
+
function Application({ behaviors }) {
|
|
26
|
+
const { host, surfaces } = useInteractionHosts();
|
|
27
|
+
const dispatcher = useMemo(() => createBrowserDispatcher({
|
|
28
|
+
...host,
|
|
29
|
+
executeCommand: async (command) => {
|
|
30
|
+
executed.push(command);
|
|
31
|
+
return { isSuccess: true };
|
|
32
|
+
},
|
|
33
|
+
}), [host]);
|
|
34
|
+
return (_jsxs(InteractionScope, { dispatcher: dispatcher, attachments: [], children: [_jsx(Button, { behaviors: behaviors }), surfaces] }));
|
|
35
|
+
}
|
|
36
|
+
describe('when a document asks the user something', () => {
|
|
37
|
+
beforeEach(() => { executed.length = 0; });
|
|
38
|
+
it('should ask in the page rather than in a browser prompt', async () => {
|
|
39
|
+
render(_jsx(Application, { behaviors: [confirmThenExecute] }));
|
|
40
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
41
|
+
screen.getByRole('alertdialog').getAttribute('aria-label').should.equal('Cancel this invoice?');
|
|
42
|
+
});
|
|
43
|
+
it('should not run what follows until the question is answered', async () => {
|
|
44
|
+
render(_jsx(Application, { behaviors: [confirmThenExecute] }));
|
|
45
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
46
|
+
executed.should.have.lengthOf(0);
|
|
47
|
+
});
|
|
48
|
+
it('should run what follows when the answer is yes', async () => {
|
|
49
|
+
render(_jsx(Application, { behaviors: [confirmThenExecute] }));
|
|
50
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
51
|
+
await userEvent.click(screen.getByRole('button', { name: 'OK' }));
|
|
52
|
+
executed.should.eql(['CancelInvoice']);
|
|
53
|
+
});
|
|
54
|
+
it('should abandon the sequence when the answer is no', async () => {
|
|
55
|
+
render(_jsx(Application, { behaviors: [confirmThenExecute] }));
|
|
56
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
57
|
+
await userEvent.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Cancel' }));
|
|
58
|
+
executed.should.have.lengthOf(0);
|
|
59
|
+
});
|
|
60
|
+
it('should take the question down once it is answered', async () => {
|
|
61
|
+
render(_jsx(Application, { behaviors: [confirmThenExecute] }));
|
|
62
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
63
|
+
await userEvent.click(screen.getByRole('button', { name: 'OK' }));
|
|
64
|
+
(screen.queryByRole('alertdialog') === null).should.equal(true);
|
|
65
|
+
});
|
|
66
|
+
it('should show what the document said after the command ran', async () => {
|
|
67
|
+
render(_jsx(Application, { behaviors: [confirmThenExecute] }));
|
|
68
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
69
|
+
await userEvent.click(screen.getByRole('button', { name: 'OK' }));
|
|
70
|
+
screen.getByText('Invoice cancelled').textContent.should.equal('Invoice cancelled');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=when_a_document_asks_the_user_something.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when_a_document_asks_the_user_something.js","sourceRoot":"","sources":["../../../for_useInteractionHosts/when_a_document_asks_the_user_something.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when_the_host_cannot_do_what_was_asked.d.ts","sourceRoot":"","sources":["../../../for_useInteractionHosts/when_the_host_cannot_do_what_was_asked.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { InteractionActionKind, InteractionTriggerKind } from '@cratis/scene.model';
|
|
6
|
+
import { InteractionScope, createBrowserDispatcher, useInteractionHosts, useInteractions } from '../interactions';
|
|
7
|
+
const navigateSomewhere = {
|
|
8
|
+
name: 'GoToInvoice',
|
|
9
|
+
bindings: [
|
|
10
|
+
{
|
|
11
|
+
trigger: { kind: InteractionTriggerKind.Click },
|
|
12
|
+
actions: [{ kind: InteractionActionKind.Navigate, screen: 'InvoiceDetails' }],
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
function Button() {
|
|
17
|
+
const handlers = useInteractions('element', [navigateSomewhere]);
|
|
18
|
+
return _jsx("button", { type: "button", ...handlers, children: "Open" });
|
|
19
|
+
}
|
|
20
|
+
function Application() {
|
|
21
|
+
const { host, surfaces } = useInteractionHosts();
|
|
22
|
+
const dispatcher = useMemo(() => createBrowserDispatcher(host), [host]);
|
|
23
|
+
return (_jsxs(InteractionScope, { dispatcher: dispatcher, attachments: [], children: [_jsx(Button, {}), surfaces] }));
|
|
24
|
+
}
|
|
25
|
+
describe('when the host cannot do what was asked', () => {
|
|
26
|
+
it('should say so on the screen rather than do nothing', async () => {
|
|
27
|
+
render(_jsx(Application, {}));
|
|
28
|
+
await userEvent.click(screen.getByRole('button', { name: 'Open' }));
|
|
29
|
+
screen.getByText('navigate to InvoiceDetails').textContent.should.equal('navigate to InvoiceDetails');
|
|
30
|
+
});
|
|
31
|
+
it('should report the same action once however often it is asked for', async () => {
|
|
32
|
+
render(_jsx(Application, {}));
|
|
33
|
+
await userEvent.click(screen.getByRole('button', { name: 'Open' }));
|
|
34
|
+
await userEvent.click(screen.getByRole('button', { name: 'Open' }));
|
|
35
|
+
await userEvent.click(screen.getByRole('button', { name: 'Open' }));
|
|
36
|
+
screen.getAllByText('navigate to InvoiceDetails').should.have.lengthOf(1);
|
|
37
|
+
});
|
|
38
|
+
it('should say nothing when nothing has failed', () => {
|
|
39
|
+
render(_jsx(Application, {}));
|
|
40
|
+
(screen.queryByText(/cannot do what the document asked/) === null).should.equal(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=when_the_host_cannot_do_what_was_asked.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when_the_host_cannot_do_what_was_asked.js","sourceRoot":"","sources":["../../../for_useInteractionHosts/when_the_host_cannot_do_what_was_asked.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,SAAS,MAAM,6BAA6B,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAY,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElH,MAAM,iBAAiB,GAAa;IAChC,IAAI,EAAE,aAAa;IACnB,QAAQ,EAAE;QACN;YACI,OAAO,EAAE,EAAE,IAAI,EAAE,sBAAsB,CAAC,KAAK,EAAW;YACxD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAW,CAAC;SACzF;KACJ;CACJ,CAAC;AAEF,SAAS,MAAM;IACX,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACjE,OAAO,iBAAQ,IAAI,EAAC,QAAQ,KAAK,QAAQ,qBAAe,CAAC;AAC7D,CAAC;AAKD,SAAS,WAAW;IAChB,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACjD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAExE,OAAO,CACH,MAAC,gBAAgB,IAAC,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,aACrD,KAAC,MAAM,KAAG,EACT,QAAQ,IACM,CACtB,CAAC;AACN,CAAC;AAED,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACpD,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,CAAC,KAAC,WAAW,KAAG,CAAC,CAAC;QAExB,MAAM,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAEpE,MAAM,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC,WAAY,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAC3G,CAAC,CAAC,CAAC;IAIH,EAAE,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,CAAC,KAAC,WAAW,KAAG,CAAC,CAAC;QAExB,MAAM,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QACpE,MAAM,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QACpE,MAAM,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAEpE,MAAM,CAAC,YAAY,CAAC,4BAA4B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,KAAC,WAAW,KAAG,CAAC,CAAC;QAExB,CAAC,MAAM,CAAC,WAAW,CAAC,mCAAmC,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
package/dist/esm/for_useInteractions/when_clicking_an_element_that_executes_a_command.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when_clicking_an_element_that_executes_a_command.d.ts","sourceRoot":"","sources":["../../../for_useInteractions/when_clicking_an_element_that_executes_a_command.tsx"],"names":[],"mappings":""}
|