Skip to content

Make tags that scroll out of view reachable in the tag field - #514

Draft
tonygermano wants to merge 9 commits into
OpenIntegrationEngine:mainfrom
tonygermano:feat/tag-field-edit-mode
Draft

tonygermano wants to merge 9 commits into
OpenIntegrationEngine:mainfrom
tonygermano:feat/tag-field-edit-mode

Conversation

@tonygermano

Copy link
Copy Markdown
Member

Summary

Follow-up to #437. When a tag field has more tags than fit, the first ones scroll past the left edge, and there was no way to get back to them (see @NicoPiel's review). In the channel editor, which has no clear button, the only way to remove the first tag was to backspace through every tag after it.

This PR makes every tag reachable from both the mouse and the keyboard, and removes a license notice #437 left behind. It applies to all three tag fields: the channel editor and the Dashboard and Channels filters.

Changes

Keyboard navigation with an edit mode. The text editor can now sit between chips, and new tags are inserted where it is. Typing enters an edit mode, shown by an outline around the text, and the keys behave differently in each mode:

Key Navigating Edit mode
Left / Right Move the editor past a chip Move the caret
Home / End Move the editor to the first or last slot Move the caret
Backspace / Delete Remove the chip before or after the editor Edit the text
Enter / comma Nothing Commit the text
Escape Passed on to the window Clear the text and leave edit mode without committing

Edit mode lasts until the text is committed, Escape is pressed, or the field loses focus with no text. Deleting the text doesn't end it, so holding Backspace to clear what you typed can't run on into the chips.

Mouse wheel scrolling. The wheel and sideways trackpad swipes scroll the chips. When all the chips fit, the wheel event is passed on, so an enclosing scroll pane still scrolls. Before, the field swallowed it.

Mouse deletion and placement.

  • Like a browser tab, a middle click anywhere on a chip deletes it.
  • The "×" deletes on release, and only if the pointer is still over it.
  • After a mouse delete, the row stays where it is, so the next chip slides under the pointer.
  • A left click in a gap between chips puts the editor there, so tags can be inserted with the mouse.

Third-party notice. #437 removed the last bundled copy of bootstrap-tokenfield, so its section of THIRD-PARTY-README.txt is removed. The jQuery and Bootstrap sections stay, since server/public_api_html still ships both.

Commits

Each commit builds on its own.

  1. Navigate tag chips from the keyboard with an edit mode. The editor sits between chips, edit mode works as described above, and the editor is sized to its text. The editor and its outline use TagChip's font and corner radius, so typed text looks like the chips.
  2. Drop the bootstrap-tokenfield third-party notice.
  3. Scroll the tag chips with the mouse wheel. This includes sideways scrolling on Linux. X11 reports it as mouse buttons 4 and 5 rather than as wheel events, so it needs its own listener.
  4. Delete tag chips with a middle click and place the editor by clicking.

Testing

  • I tested the channel editor and the Dashboard and Channels filters on Linux (X11) with a trackpad, a trackpoint, and a mouse with a tilt wheel. The mouse's back and forward buttons have no effect on the field.
  • On Windows, I tested scrolling with the tilt-wheel mouse.
  • I haven't tested on macOS. There, sideways scrolling arrives as wheel events with Shift, the same as on Windows.
  • ./gradlew clean build passes.
  • Claude checked the keyboard, mouse and scrolling behavior with a headless test harness, which isn't part of this PR. A separate Claude review with no prior context checked each commit on its own. The fixes from that review are included.

Drafted with Claude Code

@tonygermano tonygermano added the enhancement New feature or request label Oct 26, 2026
@tonygermano
tonygermano requested a review from a team October 26, 2026 03:10
@tonygermano tonygermano added the licensing Eclipse Intellectual Property (IP) due diligence: licensing, provenance, Dash results, IP reviews label Oct 26, 2026
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Test Results

129 files  ± 0  129 suites  ±0   3m 21s ⏱️ +24s
789 tests +52  789 ✅ +52  0 💤 ±0  0 ❌ ±0 
915 runs  +88  909 ✅ +88  6 💤 ±0  0 ❌ ±0 

Results for commit 35f20bb. ± Comparison against base commit 6248cdd.

This pull request removes 2 and adds 54 tests. Note that renamed tests count towards both.
com.mirth.connect.donkey.server.data.jdbc.OracleJdbcDaoTest ‑ testBatchInsertMessageContentKeepsStatementOpenUntilExecuted
com.mirth.connect.donkey.server.data.jdbc.OracleJdbcDaoTest ‑ testFailedBatchInsertClearsAndClosesTheStatement
com.mirth.connect.connectors.tcp.SocketUtilTest ‑ setKeepAliveDisablesKeepAliveAndLeavesIdle
com.mirth.connect.connectors.tcp.SocketUtilTest ‑ setKeepAliveEnablesKeepAliveAndCapsIdle
com.mirth.connect.donkey.server.data.jdbc.JdbcDaoTest ‑ insertMessageContentBatchBatchesEveryRowThenExecutesOnce
com.mirth.connect.donkey.server.data.jdbc.JdbcDaoTest ‑ insertMessageContentBatchClosesTheStatementWhenBindingFails
com.mirth.connect.server.userutil.DestinationSetTest ‑ test_addAll_emptyCollection_throwsUnsupportedOperationException
com.mirth.connect.server.userutil.DestinationSetTest ‑ test_addAll_throwsUnsupportedOperationException
com.mirth.connect.server.userutil.DestinationSetTest ‑ test_add_throwsUnsupportedOperationException
com.mirth.connect.server.userutil.DestinationSetTest ‑ test_clear_noSourceMap
com.mirth.connect.server.userutil.DestinationSetTest ‑ test_clear_withSourceMap_removesAll
com.mirth.connect.server.userutil.DestinationSetTest ‑ test_containsAll_nullCollection_throwsNullPointerException
…

♻️ This comment has been updated with latest results.

mgaffigan
mgaffigan previously approved these changes Oct 26, 2026

@mgaffigan mgaffigan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tags-control.mov

@NicoPiel

Copy link
Copy Markdown
Contributor

Bugs:

  1. Typed text carries over to the next channel. Text typed but not committed stays in the editor, still in edit mode, when you open another channel: editChannel → setChannelTags never clears it. Saving the second channel then adds that text as a tag via createTagOnFocusLost.
  2. Editor stuck between chips. If you type between two chips and click Save, the tag is added but the editor stays where it was, empty and unfocused, with no prompt.

Lower priority:

  • getPreferredSize reaches the editor through the outer editor field, which is still null while the text field is being built. It's a possible null pointer error if the size is ever asked for that early.
  • editorIndex is reset by hand before setTokens in three places, but update() and clear() don't do it. One reset inside setTokens would cover all of them.
  • moveEditor rebuilds every chip on each arrow-key press. setComponentZOrder would move the editor and keep focus.
  • On X11, each tag field installs its own app-wide mouse listener, so every mouse event in the client goes through up to three of them.
  • The edit outline's color comes from MetalLookAndFeel rather than UIManager.

gibson9583
gibson9583 previously approved these changes Oct 30, 2026

@gibson9583 gibson9583 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks safe to merge.

The editor can now sit between chips, and commits insert at its
position. Typing enters an outlined edit mode, in which the keys act on
the text instead of the chips:

- Left / Right
  - Navigating: move the editor past a chip
  - Edit mode: move the caret
- Home / End
  - Navigating: move the editor to the first or last slot
  - Edit mode: move the caret
- Backspace / Delete
  - Navigating: remove the chip before or after the editor
  - Edit mode: edit the text
- Enter / comma
  - Navigating: nothing
  - Edit mode: commit the text
- Escape
  - Navigating: passed on to the window
  - Edit mode: clear the text and leave edit mode without committing

Edit mode lasts until the text is committed, Escape is pressed, or the
field loses focus with no text. Deleting the text doesn't end it, so
holding Backspace to clear the text can't run on into the chips.

A click on the row outside the chips moves the editor back to the end.
Deleting a chip from the keyboard scrolls the editor into view, but
deleting one with the mouse leaves the row where it is, so the next
chip slides under the pointer.

The editor is sized to its text and grows while typing; it fills the
rest of the row only while showing its prompt. TagChip's font and
corner arc are shared so the editor and its outline match the chips.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Tony Germano <tony@germano.name>
The Swing rewrite of the tag field removed the last bundled copy of
bootstrap-tokenfield, so its license notice no longer applies. The
jQuery and Bootstrap notices stay, since server/public_api_html still
ships both.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Tony Germano <tony@germano.name>
The chip row has no scrollbar, so the scroll pane's own wheel handling
did nothing, and tags scrolled off the left edge could not be reached
with the mouse. The wheel, or a sideways trackpad swipe, now scrolls
the row horizontally. While the chips fit, the event is passed on so
an enclosing scroll pane still scrolls; before, the field swallowed it.

On X11, horizontal scrolling arrives as presses of mouse buttons 4 and
5 rather than as wheel events, so a toolkit event listener scrolls the
row for those while the field is displayable. Buttons other than the
left one no longer focus the editor or move it to the end; otherwise
each sideways scroll step would pull the row back to the editor.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Tony Germano <tony@germano.name>
Like a browser tab, a middle click anywhere on a chip deletes it. The
"×" deletes on a left or middle click and ignores other buttons, which
on X11 include horizontal scrolling. Both act on release, and only if
the pointer is still over what was pressed; a click event is dropped if
the pointer moves while the button is down, which a trackpoint nearly
always does.

A left click in a gap between chips, or past the last one, puts the
editor in that gap, so tags can be inserted with the mouse. Clicks on a
chip itself don't move the editor, and neither does any click while
text is being edited or the field is disabled.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Tony Germano <tony@germano.name>
Clear uncommitted text and edit mode when another channel is loaded.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Tony Germano <tony@germano.name>
Move the editor back to the end when Save commits text while unfocused.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Tony Germano <tony@germano.name>
Guard isShowingPrompt() against a null editor during construction.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Tony Germano <tony@germano.name>
Move the editor with setComponentZOrder instead of rebuilding chips.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Tony Germano <tony@germano.name>
Read the outline color from UIManager on each paint, not Metal.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Tony Germano <tony@germano.name>
@tonygermano
tonygermano dismissed stale reviews from gibson9583 and mgaffigan via 35f20bb October 31, 2026 08:20
@tonygermano
tonygermano force-pushed the feat/tag-field-edit-mode branch from 91ed8fc to 35f20bb Compare October 31, 2026 08:20
@tonygermano
tonygermano marked this pull request as draft October 31, 2026 08:24
@tonygermano

Copy link
Copy Markdown
Member Author

Note

I marked as draft because I pushed each point as a separate fixup! commit so that the individual fixes were easy to make out from the previous code. I wanted to make sure those commits were autosquashed after review and before merging, but I don't plan on making any more changes.

@NicoPiel Thanks for the careful look. All of these are addressed in the latest push, either with a fix or an explanation below.

Bugs:

  1. Typed text carries over to the next channel. Fixed: setChannelTags now clears the editor and leaves edit mode. The text already carried over before this PR, but edit mode made it much easier to notice.
  2. Editor stuck between chips. Fixed: when createTagOnFocusLost commits while the editor doesn't have focus, as with Save, it moves the editor back to the end, where the prompt shows again. If you save with Ctrl+S while still typing, the editor keeps focus and stays where it is.

Lower priority:

  • getPreferredSize before editor is set. Fixed: isShowingPrompt() treats a null editor as unfocused.
  • Resetting editorIndex in setTokens. I left this one as is, on purpose. The three places that reset it by hand load a different set of tags, so the editor goes to the end. update() re-resolves the same tags on every Dashboard refresh. Resetting there would pull the editor back to the end every few seconds while someone is moving it or typing. clear() empties the tags, so the clamp in setTokens already puts the editor at 0, which is the end.
  • moveEditor rebuilding every chip. Done: it now moves the editor with setComponentZOrder and leaves the chips alone.
  • One app-wide listener per field on X11. Only one tag field is displayable at a time. Frame.setCurrentContentPage removes the previous page from the viewport, and removeNotify takes that field's listener with it. The listener checks only the event ID and button before returning, so the cost per mouse event is negligible.
  • Outline color from MetalLookAndFeel. Done: the outline reads UIManager.getColor("Button.focus") each time it paints, so it follows the look and feel. Under the client's Plastic theme, it's the same color as before.

Drafted with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request licensing Eclipse Intellectual Property (IP) due diligence: licensing, provenance, Dash results, IP reviews

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants