Skip to content

Updates Image Editor so that cropper move and resize are keyboard-accessible #17073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
66092d2
Make cropper container keyboard focusable
gcamacho079 Mar 26, 2025
d4f0b04
Move toggle function, add keydown listener for cropper
gcamacho079 Mar 26, 2025
d5c39e3
Commit
gcamacho079 Mar 31, 2025
f0a3994
Merge branch '5.7' into feature/pt-475-crop-frame-cannot-be-moved-usi…
gcamacho079 Apr 11, 2025
98aab89
Remove default focus outline from cropping canvas, make cropping rect…
gcamacho079 Apr 11, 2025
ad12b4d
Exit keydown handler if not a directional key
gcamacho079 Apr 11, 2025
1e74d0f
Update cropper rectangle placement based on keyboard input
gcamacho079 Apr 14, 2025
9aca6ee
Remove unused function
gcamacho079 Apr 14, 2025
b34ca22
Reuse code for both drag and keyboard move
gcamacho079 Apr 14, 2025
24c329e
Start moving to button versus having the canvas be focuable
gcamacho079 Apr 15, 2025
fd7c25a
Toggle button state on activate, toggle picked up mode
gcamacho079 Apr 15, 2025
79c68fe
Add temp announcer to troubleshoot sr messages, when we add those in
gcamacho079 Apr 16, 2025
cfc1a5c
Find position of cropper center
gcamacho079 Apr 16, 2025
faad842
Fix bug where arrows were still activating tab change, move cropping …
gcamacho079 Apr 16, 2025
d2de5c7
Add button macro and use for all corner resize and crop moving buttons
gcamacho079 Apr 17, 2025
7595227
Pass corner info to data attribute on resize handle button
gcamacho079 Apr 21, 2025
420219b
Merge 5.8 and build
gcamacho079 Apr 21, 2025
556d5d2
Start refactoring for exclusive button functionality
gcamacho079 Apr 21, 2025
44d7d5b
Track which corner is picked up and change handle color (WIP)
gcamacho079 Apr 24, 2025
663cf3a
Add circle around selected corner based on which one has been picked up
gcamacho079 Apr 24, 2025
c3dc123
Make corner focus ring more apparent and add new fxn for getting corn…
gcamacho079 Apr 24, 2025
660cd22
Refactor position message code
gcamacho079 Apr 24, 2025
f649e64
Clean up styles and containers
gcamacho079 Apr 25, 2025
1e0fffc
Toggle directional controls when a handle or the crop rectangle are p…
gcamacho079 Apr 25, 2025
dc81003
Move crop rectangle on directional arrow press
gcamacho079 Apr 25, 2025
aafa346
Get resize working for keyboard; TODO add edges as handles
gcamacho079 Apr 28, 2025
0062459
Make corners move on directional keypress, combine handlePickedUp wit…
gcamacho079 Apr 29, 2025
44ab675
Revert "Make corners move on directional keypress, combine handlePick…
gcamacho079 May 5, 2025
7d80d16
Rename twig corner property to handle
gcamacho079 May 5, 2025
86c8d49
Rename focus outline property to refer to handle, not just corner
gcamacho079 May 5, 2025
86f6a83
Rename handle data attribute
gcamacho079 May 5, 2025
2058034
Remove duplicated code, rename some methods
gcamacho079 May 5, 2025
6957b11
Replace handleClicked with handlePicked
gcamacho079 May 5, 2025
3697536
Change more references of corner to handle
gcamacho079 May 5, 2025
5395601
Add buttons for new handles (t, b, l, r)
gcamacho079 May 7, 2025
1e38c08
Update handle names to include hyphens
gcamacho079 May 7, 2025
628e985
Add other directional handles
gcamacho079 May 7, 2025
b018092
Position focus outlines for b/t/l/r on button press
gcamacho079 May 7, 2025
d2f1a3d
Remove hyphens temporarily because they are causing this to not work
gcamacho079 May 7, 2025
5bfced9
Rename class names to reference handles instead of corners; start add…
gcamacho079 May 12, 2025
e9bb715
Add listener to edit buttons; TODO add focus styles on focus, update …
gcamacho079 May 14, 2025
672c6dd
Add cropper rectangle/handle focus styles on button focus
gcamacho079 May 14, 2025
6d8955b
Add two-color focus styles to handles
gcamacho079 May 14, 2025
4e5d2ab
Add persistent active style (TODO: make better) when focus leaves the…
gcamacho079 May 16, 2025
9a483b4
Add disclosure toggle for visible on-screen controls
gcamacho079 May 19, 2025
eb8e115
Make sure directional arrows are not in the focus order when the on-s…
gcamacho079 May 19, 2025
bfa8240
Start pulling out common styles for focus ring on handles
gcamacho079 May 20, 2025
5fb6c97
Add additional arrow icons for corner-specific buttons
gcamacho079 May 21, 2025
22b2993
Consolidate focus/active styles into one function
gcamacho079 May 21, 2025
3837d08
Add icon to active indicator when picked up (TODO: only shows when as…
gcamacho079 May 21, 2025
94ff687
Rearrance conditional statement
gcamacho079 May 21, 2025
8676c7a
Use focusin instead of focus for changing focus styles
gcamacho079 May 21, 2025
013f875
Reintroduce separate active/focused handle indicator props
gcamacho079 May 21, 2025
4951bff
Refactor to include pick up and drop functions, and drop cropper elem…
gcamacho079 May 27, 2025
f219873
Add temp announcement in resize fxn
gcamacho079 May 27, 2025
67e9fe8
A bit more code cleanup
gcamacho079 May 28, 2025
1a370ac
More cleanup of functions
gcamacho079 May 28, 2025
bb26b56
Add message for size and position when resizing the rectangle
gcamacho079 May 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/copyicons.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@
'solid/apple-core.svg',
'solid/apple-whole.svg',
'solid/archway.svg',
'solid/arrow-down-left.svg',
'solid/arrow-down-right.svg',
'solid/arrow-down-short-wide.svg',
'solid/arrow-down-wide-short.svg',
'solid/arrow-down.svg',
Expand All @@ -124,6 +126,8 @@
'solid/arrow-trend-down.svg',
'solid/arrow-trend-up.svg',
'solid/arrow-turn-down-left.svg',
'solid/arrow-up-left.svg',
'solid/arrow-up-right.svg',
'solid/arrow-up-right-from-square.svg',
'solid/arrow-up.svg',
'solid/arrows-rotate-reverse.svg',
Expand Down
24 changes: 24 additions & 0 deletions src/icons/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,18 @@
'terms' => ' downwards arrow download ',
'pro' => false,
),
'arrow-down-left' =>
array(
'name' => ' arrow down left ',
'terms' => ' diagonal southwest ',
'pro' => true,
),
'arrow-down-right' =>
array(
'name' => ' arrow down right ',
'terms' => ' diagonal southeast ',
'pro' => true,
),
'arrow-down-short-wide' =>
array(
'name' => ' arrow down short wide ',
Expand Down Expand Up @@ -337,6 +349,18 @@
'terms' => ' upwards arrow forward upgrade upload ',
'pro' => false,
),
'arrow-up-left' =>
array(
'name' => ' arrow up left ',
'terms' => ' diagonal northwest ',
'pro' => true,
),
'arrow-up-right' =>
array(
'name' => ' arrow up right ',
'terms' => ' diagonal northeast ',
'pro' => true,
),
'arrow-up-right-from-square' =>
array(
'name' => ' arrow up right from square ',
Expand Down
1 change: 1 addition & 0 deletions src/icons/solid/arrow-down-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/icons/solid/arrow-down-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/icons/solid/arrow-up-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/icons/solid/arrow-up-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
147 changes: 147 additions & 0 deletions src/templates/_special/image_editor.twig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% import "_includes/forms" as forms %}
{% set orientation = craft.app.locale.getOrientation() -%}
{% do view.registerTranslations('app', [
'Original',
'Square',
Expand All @@ -8,6 +9,22 @@
class: 'visually-hidden',
text: 'Edit Image'|t('app'),
}) }}

{% macro iconButton(config) %}
{% set id = config.id ?? null %}
{% tag 'button' with {
id: id,
class: ['btn', 'flex', 'flex-nowrap', 'gap-s'],
type: 'button',
aria: {
pressed: 'false',
},
}|merge(config.buttonAttributes ?? {}, recursive=true) %}
<div class="cp-icon puny">{{ iconSvg(config.icon) }}</div>
<div data-item-name>{{ config.label }}</div>
{% endtag %}
{% endmacro %}

<div class="tabs">
<ul role="tablist">
<li id="rotate-tab" data-view="rotate" role="tab" tabindex="0" aria-selected="true"><i></i>{{ 'Rotate'|t('app') }}</li>
Expand Down Expand Up @@ -60,6 +77,136 @@
value: 'none',
options: constraintOptions,
}) }}

{% set cropHandleBtns = [
{
label: orientation == 'ltr' ? 'Top Left Handle'|t('app') : 'Top Right Handle'|t('app'),
handle: orientation == 'ltr' ? 'tl' : 'tr',
icon: orientation == 'ltr' ? 'arrow-up-left' : 'arrow-up-right',
},
{
label: 'Top Handle'|t('app'),
handle: 't',
icon: 'arrow-up',
},
{
label: orientation == 'ltr' ? 'Top Right Handle'|t('app') : 'Top Left Handle'|t('app'),
handle: orientation == 'ltr' ? 'tr' : 'tl',
icon: orientation == 'ltr' ? 'arrow-up-right' : 'arrow-up-left',
},
{
label: orientation == 'ltr' ? 'Left Handle'|t('app') : 'Right Handle'|t('app'),
handle: orientation == 'ltr' ? 'l' : 'r',
icon: orientation == 'ltr' ? 'arrow-left' : 'arrow-right',
},
{
label: orientation == 'ltr' ? 'Right Handle'|t('app') : 'Left Handle'|t('app'),
handle: orientation == 'ltr' ? 'r' : 'l',
icon: orientation == 'ltr' ? 'arrow-right' : 'arrow-left',
},
{
label: orientation == 'ltr' ? 'Bottom Left Handle'|t('app') : 'Bottom Right Handle'|t('app'),
handle: orientation == 'ltr' ? 'bl' : 'br',
icon: orientation == 'ltr' ? 'arrow-down-left' : 'arrow-down-right',
},
{
label: 'Bottom Handle'|t('app'),
handle: 'b',
icon: 'arrow-down',
},
{
label: orientation == 'ltr' ? 'Bottom Right Handle'|t('app') : 'Bottom Left Handle'|t('app'),
handle: orientation == 'ltr' ? 'br' : 'bl',
icon: orientation == 'ltr' ? 'arrow-down-right' : 'arrow-down-left',
},
] %}

{% set directionalButtons = [
{
label: 'Up'|t('app'),
direction: 'up',
},
{
label: orientation == 'ltr' ? 'Left'|t('app') : 'Right'|t('app'),
direction: orientation == 'ltr' ? 'left' : 'right',
},
{
label: 'Down'|t('app'),
direction: 'down',
},
{
label: orientation == 'ltr' ? 'Right'|t('app') : 'Left'|t('app'),
direction: orientation == 'ltr' ? 'right' : 'left',
},
] %}
<craft-disclosure>
{{ _self.iconButton({
icon: 'eye',
label: 'Crop Edit Controls'|t('app'),
buttonAttributes: {
'aria-controls': 'cropper-edit'
}
}) }}
</craft-disclosure>

<fieldset id="cropper-edit" class="cropper-edit">
<div id="move-icon-wrapper" style="display: none;">{{ iconSvg('up-down-left-right') }}</div>
<legend class="cropper-edit__legend">
{{ 'Edit {type}'|t('app', {
type: 'Cropping Rectangle'|t('app'),
}) }}
</legend>

<div class="cropper-edit__wrapper">
{{ _self.iconButton({
icon: 'frame',
label: 'Cropping Rectangle'|t('app'),
id: 'cropper-handle',
buttonAttributes: {
data: {
'crop-editor': 'rectangle',
},
class: ['cropper-edit__btn']
}
}) }}
</div>

<div class="cropper-edit__wrapper cropper-edit__wrapper--handles">
{% for button in cropHandleBtns %}
{% if loop.index == 5 %}
<div></div>
{% endif %}
{{ _self.iconButton({
icon: button.icon,
label: button.label,
buttonAttributes: {
data: {
'crop-editor': button.handle,
},
class: ['cropper-edit__btn']
}
}) }}
{% endfor %}
</div>

<div class="cropper-edit__wrapper cropper-edit__wrapper--arrows directional-buttons hidden">
{% for button in directionalButtons %}
{% tag 'button' with {
class: ['btn'],
type: 'button',
aria: {
label: button.label,
},
data: {
direction: button.direction,
}
} %}
<div class="cp-icon puny">{{ iconSvg(button.direction) }}</div>
{% endtag %}
{% endfor %}
</div>

</fieldset>
</div>
</div>
<div class="image-container">
Expand Down
11 changes: 11 additions & 0 deletions src/translations/en/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,11 @@
'Blue' => 'Blue',
'Body' => 'Body',
'Bottom-Center' => 'Bottom-Center',
'Bottom Handle' => 'Bottom Handle',
'Bottom-Left' => 'Bottom-Left',
'Bottom-Left Handle' => 'Bottom-Left Handle',
'Bottom-Right' => 'Bottom-Right',
'Bottom-Right Handle' => 'Bottom-Right Handle',
'Branch Limit' => 'Branch Limit',
'Breadcrumbs' => 'Breadcrumbs',
'Briefly describe your issue or idea.' => 'Briefly describe your issue or idea.',
Expand Down Expand Up @@ -488,6 +491,7 @@
'Credentialed' => 'Credentialed',
'Critical' => 'Critical',
'Crop' => 'Crop',
'Cropping Rectangle' => 'Cropping Rectangle',
'Currency' => 'Currency',
'Current Password' => 'Current Password',
'Current User Condition' => 'Current User Condition',
Expand Down Expand Up @@ -925,6 +929,7 @@
'Leave blank if entries don’t have URLs' => 'Leave blank if entries don’t have URLs',
'Leave blank if the entry doesn’t have a URL' => 'Leave blank if the entry doesn’t have a URL',
'Leave it uninstalled' => 'Leave it uninstalled',
'Left Handle' => 'Left Handle',
'Let each entry choose which sites it should be saved to' => 'Let each entry choose which sites it should be saved to',
'Letterbox' => 'Letterbox',
'Level {num}' => 'Level {num}',
Expand Down Expand Up @@ -1395,6 +1400,7 @@
'Revision' => 'Revision',
'Revisions for “{title}”' => 'Revisions for “{title}”',
'Revisions' => 'Revisions',
'Right Handle' => 'Right Handle',
'Rose' => 'Rose',
'Rotate Left' => 'Rotate Left',
'Rotate Right' => 'Rotate Right',
Expand Down Expand Up @@ -1807,8 +1813,11 @@
'Toggle sidebar' => 'Toggle sidebar',
'Tokens' => 'Tokens',
'Top-Center' => 'Top-Center',
'Top Handle' => 'Top Handle',
'Top-Left' => 'Top-Left',
'Top-Left Handle' => 'Top-Left Handle',
'Top-Right' => 'Top-Right',
'Top-Right Handle' => 'Top-Right Handle',
'Total Price' => 'Total Price',
'Total releases' => 'Total releases',
'Townland' => 'Townland',
Expand Down Expand Up @@ -1907,6 +1916,7 @@
'Use defaults' => 'Use defaults',
'Use for element thumbnails' => 'Use for element thumbnails',
'Use shapes to represent statuses' => 'Use shapes to represent statuses',
'Use the arrow keys to change position, Tab or Spacebar to drop.' => 'Use the arrow keys to change position, Tab or Spacebar to drop.',
'Use the loaded project config' => 'Use the loaded project config',
'Use this field’s values as search keywords' => 'Use this field’s values as search keywords',
'Used by' => 'Used by',
Expand Down Expand Up @@ -2213,6 +2223,7 @@
'{filename} isn’t selectable for this field.' => '{filename} isn’t selectable for this field.',
'{first, number}-{last, number} of {total, number} {total, plural, =1{{item}} other{{items}}}' => '{first, number}–{last, number} of {total, number} {total, plural, =1{{item}} other{{items}}}',
'{first}-{last} of {total}' => '{first}–{last} of {total}',
'{item} picked up.' => '{item} picked up.',
'{max, plural, =1{Author} other {Authors}}' => '{max, plural, =1{Author} other {Authors}}',
'{names} {total, plural, =1{is installed as a trial} other{are installed as trials}}.' => '{names} {total, plural, =1{is installed as a trial} other{are installed as trials}}.',
'{name} Setup' => '{name} Setup',
Expand Down
2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/css/cp.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/css/cp.css.map

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions src/web/assets/cp/src/css/_image_editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,50 @@ html.noscroll body {
@include mixins.light-focus-ring;
opacity: 0;
user-select: none;

.cropper-edit {
display: grid;
max-width: 400px;
grid-gap: var(--s);

&:not([data-state='expanded']) {
@include mixins.visually-hidden;
}

&[data-state='collapsed'] {
.directional-buttons {
display: none;
}
}
}

.cropper-edit__wrapper--handles {
display: grid;
grid-template-columns: repeat(3, 1fr);
}

.cropper-edit__wrapper--handles,
.cropper-edit__wrapper--arrows {
grid-gap: var(--xs);
}

.cropper-edit__wrapper--arrows {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-areas:
'. up .'
'dir dir dir';

[data-direction='up'] {
grid-area: up;
}

[data-direction='down'],
[data-direction='left'],
[data-direction='right'] {
grid-row-start: 2;
}
}
}

.modal.imageeditor.modal {
Expand Down Expand Up @@ -530,3 +574,7 @@ html.noscroll body {
}
}
}

#cropping-canvas {
--focus-ring: none;
}
Loading