Skip to content

Commit 7e636fd

Browse files
lukewarloweps1lon
authored andcommitted
Add support for command invokers API
1 parent aac177c commit 7e636fd

File tree

11 files changed

+98
-0
lines changed

11 files changed

+98
-0
lines changed

fixtures/attribute-behavior/src/attributes.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,21 @@ const attributes = [
357357
},
358358
{name: 'cols', tagName: 'textarea'},
359359
{name: 'colSpan', containerTagName: 'tr', tagName: 'td'},
360+
{name: 'command', tagName: 'button', overrideStringValue: 'show-popover'},
361+
{
362+
name: 'commandFor',
363+
read: element => {
364+
document.body.appendChild(element);
365+
try {
366+
// trigger and target need to be connected for `commandForElement` to read the actual value.
367+
return element.commandForElement;
368+
} finally {
369+
document.body.removeChild(element);
370+
}
371+
},
372+
overrideStringValue: 'popover-target',
373+
tagName: 'button',
374+
},
360375
{name: 'content', containerTagName: 'head', tagName: 'meta'},
361376
{name: 'contentEditable'},
362377
{

packages/react-dom-bindings/src/client/ReactDOMComponent.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ let didWarnFormActionTarget = false;
8282
let didWarnFormActionMethod = false;
8383
let didWarnForNewBooleanPropsWithEmptyValue: {[string]: boolean};
8484
let didWarnPopoverTargetObject = false;
85+
let didWarnCommandForObject = false;
8586
if (__DEV__) {
8687
didWarnForNewBooleanPropsWithEmptyValue = {};
8788
}
@@ -875,6 +876,21 @@ function setProp(
875876
);
876877
}
877878
}
879+
break;
880+
case 'commandFor':
881+
if (__DEV__) {
882+
if (
883+
!didWarnCommandForObject &&
884+
value != null &&
885+
typeof value === 'object'
886+
) {
887+
didWarnCommandForObject = true;
888+
console.error(
889+
'The `commandFor` prop expects the ID of an Element as a string. Received %s instead.',
890+
value,
891+
);
892+
}
893+
}
878894
// Fall through
879895
default: {
880896
if (
@@ -3077,6 +3093,10 @@ export function hydrateProperties(
30773093
}
30783094
}
30793095

3096+
if (props.onCommand != null) {
3097+
listenToNonDelegatedEvent('command', domElement);
3098+
}
3099+
30803100
if (props.onClick != null) {
30813101
// TODO: This cast may not be sound for SVG, MathML or custom elements.
30823102
trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));

packages/react-dom-bindings/src/events/DOMEventNames.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type DOMEventName =
2626
| 'change'
2727
| 'click'
2828
| 'close'
29+
| 'command'
2930
| 'compositionend'
3031
| 'compositionstart'
3132
| 'compositionupdate'

packages/react-dom-bindings/src/events/DOMEventProperties.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const simpleEventPluginEvents = [
4646
'canPlayThrough',
4747
'click',
4848
'close',
49+
'command',
4950
'contextMenu',
5051
'copy',
5152
'cut',

packages/react-dom-bindings/src/events/DOMPluginEventSystem.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ export const nonDelegatedEvents: Set<DOMEventName> = new Set([
236236
'beforetoggle',
237237
'cancel',
238238
'close',
239+
'command',
239240
'invalid',
240241
'load',
241242
'scroll',

packages/react-dom-bindings/src/events/ReactDOMEventListener.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ export function getEventPriority(domEventName: DOMEventName): EventPriority {
294294
case 'cancel':
295295
case 'click':
296296
case 'close':
297+
case 'command':
297298
case 'contextmenu':
298299
case 'copy':
299300
case 'cut':

packages/react-dom-bindings/src/events/SyntheticEvent.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,3 +600,12 @@ const ToggleEventInterface = {
600600
};
601601
export const SyntheticToggleEvent: $FlowFixMe =
602602
createSyntheticEvent(ToggleEventInterface);
603+
604+
const CommandEventInterface = {
605+
...EventInterface,
606+
source: 0,
607+
command: 0,
608+
};
609+
export const SyntheticCommandEvent: $FlowFixMe = createSyntheticEvent(
610+
CommandEventInterface,
611+
);

packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
SyntheticClipboardEvent,
2929
SyntheticPointerEvent,
3030
SyntheticToggleEvent,
31+
SyntheticCommandEvent,
3132
} from '../../events/SyntheticEvent';
3233

3334
import {
@@ -167,6 +168,9 @@ function extractEvents(
167168
// MDN claims <details> should not receive ToggleEvent contradicting the spec: https://html.spec.whatwg.org/multipage/indices.html#event-toggle
168169
SyntheticEventCtor = SyntheticToggleEvent;
169170
break;
171+
case 'command':
172+
SyntheticEventCtor = SyntheticCommandEvent;
173+
break;
170174
default:
171175
// Unknown event. This is used by createEventHandle.
172176
break;

packages/react-dom-bindings/src/shared/possibleStandardNames.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const possibleStandardNames = {
3838
classname: 'className',
3939
cols: 'cols',
4040
colspan: 'colSpan',
41+
command: 'command',
42+
commandfor: 'commandFor',
4143
content: 'content',
4244
contenteditable: 'contentEditable',
4345
contextmenu: 'contextMenu',

packages/react-dom/src/__tests__/DOMPropertyOperations-test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,34 @@ describe('DOMPropertyOperations', () => {
13461346
);
13471347
});
13481348
});
1349+
1350+
it('warns when using commandFor={HTMLElement}', async () => {
1351+
const popoverTarget = document.createElement('div');
1352+
const container = document.createElement('div');
1353+
const root = ReactDOMClient.createRoot(container);
1354+
1355+
await act(() => {
1356+
root.render(
1357+
<button key="one" commandFor={popoverTarget} command="toggle-popover">
1358+
Toggle popover
1359+
</button>,
1360+
);
1361+
});
1362+
1363+
assertConsoleErrorDev([
1364+
'The `commandFor` prop expects the ID of an Element as a string. Received HTMLDivElement {} instead.\n' +
1365+
' in button (at **)',
1366+
]);
1367+
1368+
// Dedupe warning
1369+
await act(() => {
1370+
root.render(
1371+
<button key="two" commandFor={popoverTarget} command="toggle-popover">
1372+
Toggle popover
1373+
</button>,
1374+
);
1375+
});
1376+
});
13491377
});
13501378

13511379
describe('deleteValueForProperty', () => {

0 commit comments

Comments
 (0)