feat: add FocusManager

This is the bulk of the work for introducing the central logical unit
for managing and sychronizing focus as a first-class Blockly concept
with that of DOM focus.

There's a lot to do yet, including:
- Ensuring clicks within Blockly's scope correctly sync back to focus
  changes.
- Adding support for, and testing, cases when focus is lost from all
  registered trees.
- Testing nested tree propagation.
- Testing the traverser utility class.
- Adding implementations for IFocusableTree and IFocusableNode
  throughout Blockly.
This commit is contained in:
Ben Henning
2025-03-21 00:33:51 +00:00
parent 8c25c1f8ed
commit d9beacddb4
8 changed files with 4389 additions and 1 deletions

View File

@@ -13,11 +13,82 @@
visibility: hidden;
width: 1000px;
}
.blocklyActiveFocus {
outline-color: #0f0;
outline-width: 2px;
}
.blocklyPassiveFocus {
outline-color: #00f;
outline-width: 1.5px;
}
div.blocklyActiveFocus {
color: #0f0;
}
div.blocklyPassiveFocus {
color: #00f;
}
g.blocklyActiveFocus {
fill: #0f0;
}
g.blocklyPassiveFocus {
fill: #00f;
}
</style>
<body>
<div id="mocha"></div>
<div id="failureCount" style="display: none" tests_failed="unset"></div>
<div id="failureMessages" style="display: none"></div>
<div id="testFocusableTree1" tabindex="-1">
<div id="testFocusableTree1.node1" tabindex="-1">
Tree 1 node 1
<div id="testFocusableTree1.node1.child1" tabindex="-1">Tree 1 node 1 child 1</div>
</div>
<div id="testFocusableTree1.node2" tabindex="-1">
Tree 1 node 2
<div id="testFocusableTree1.node2.unregisteredChild1" tabindex="-1">Tree 1 node 2 child 2 (unregistered)</div>
</div>
</div>
<div id="testFocusableTree2" tabindex="-1">
<div id="testFocusableTree2.node1" tabindex="-1">Tree 2 node 1</div>
</div>
<div id="testUnregisteredFocusableTree3" tabindex="-1">
<div id="testUnregisteredFocusableTree3.node1" tabindex="-1">Tree 3 node 1 (unregistered)</div>
</div>
<div id="nonTreeElementForEphemeralFocus" tabindex="-1" />
<svg width="250" height="250">
<g id="testFocusableGroup1" tabindex="-1">
<g id="testFocusableGroup1.node1" tabindex="-1">
<rect x="0" y="0" width="250" height="30" fill="grey" />
<text x="10" y="20" class="svgText">Group 1 node 1</text>
<g id="testFocusableGroup1.node1.child1" tabindex="-1">
<rect x="0" y="30" width="250" height="30" fill="lightgrey" />
<text x="10" y="50" class="svgText">Tree 1 node 1 child 1</text>
</g>
</g>
<g id="testFocusableGroup1.node2" tabindex="-1">
<rect x="0" y="60" width="250" height="30" fill="grey" />
<text x="10" y="80" class="svgText">Group 1 node 2</text>
<g id="testFocusableGroup1.node2.unregisteredChild1" tabindex="-1">
<rect x="0" y="90" width="250" height="30" fill="lightgrey" />
<text x="10" y="110" class="svgText">Tree 1 node 2 child 2 (unregistered)</text>
</g>
</g>
</g>
<g id="testFocusableGroup2" tabindex="-1">
<g id="testFocusableGroup2.node1" tabindex="-1">
<rect x="0" y="120" width="250" height="30" fill="grey" />
<text x="10" y="140" class="svgText">Group 2 node 1</text>
</g>
</g>
<g id="testUnregisteredFocusableGroup3" tabindex="-1">
<g id="testUnregisteredFocusableGroup3.node1" tabindex="-1">
<rect x="0" y="150" width="250" height="30" fill="grey" />
<text x="10" y="170" class="svgText">Tree 3 node 1 (unregistered)</text>
</g>
</g>
<g id="nonTreeGroupForEphemeralFocus" tabindex="-1"></g>
</svg>
<!-- Load mocha et al. before Blockly and the test modules so that
we can safely import the test modules that make calls
to (e.g.) suite() at the top level. -->
@@ -90,6 +161,8 @@
import './field_textinput_test.js';
import './field_variable_test.js';
import './flyout_test.js';
import './focus_manager_test.js';
// import './test_event_reduction.js';
import './generator_test.js';
import './gesture_test.js';
import './icon_test.js';