You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/08/28 04:30:14 UTC
(causeway) 12/16: feat(webcomponents): use icon property editor actions
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch htmx
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit c1a80d81c07a5c7f24454fa4fb93d7fde7820ff8
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Aug 28 00:42:21 2026 +0200
feat(webcomponents): use icon property editor actions
---
.../tasks.md | 12 ++++++------
.../foundation/src/component-styles.css | 19 +++++++++++++++++++
.../foundation/src/component-styles.mjs | 19 +++++++++++++++++++
.../foundation/src/property-element.mjs | 6 ++++--
viewers/webcomponents/foundation/src/theme.css | 22 ++++++++++++++++++++++
.../foundation/test/component-styles.test.mjs | 9 +++++++++
.../foundation/test/interaction.test.mjs | 4 ++++
7 files changed, 83 insertions(+), 8 deletions(-)
diff --git a/openspec/changes/replace-property-editor-actions-with-icons/tasks.md b/openspec/changes/replace-property-editor-actions-with-icons/tasks.md
index f6fbd9cdde7..7f3f0fb291e 100644
--- a/openspec/changes/replace-property-editor-actions-with-icons/tasks.md
+++ b/openspec/changes/replace-property-editor-actions-with-icons/tasks.md
@@ -1,11 +1,11 @@
## 1. Compact Editor Actions
-- [ ] 1.1 Replace visible Save and Cancel text with local decorative tick and cross SVGs while preserving action hooks, test identifiers, and state gating.
-- [ ] 1.2 Add property-specific accessible names and pointer tooltips and keep both SVGs outside the accessibility and focus trees.
-- [ ] 1.3 Add baseline and cohesive-theme styling for compact square editor action buttons and bounded icons while retaining primary Save treatment.
+- [x] 1.1 Replace visible Save and Cancel text with local decorative tick and cross SVGs while preserving action hooks, test identifiers, and state gating.
+- [x] 1.2 Add property-specific accessible names and pointer tooltips and keep both SVGs outside the accessibility and focus trees.
+- [x] 1.3 Add baseline and cohesive-theme styling for compact square editor action buttons and bounded icons while retaining primary Save treatment.
## 2. Verification
-- [ ] 2.1 Add interaction tests for icon-only markup, accessibility semantics, validation gating, cancellation, and focus restoration compatibility.
-- [ ] 2.2 Add style tests for compact baseline and cohesive-theme action sizing.
-- [ ] 2.3 Run focused and complete foundation tests, Petclinic browser acceptance, syntax and formatting checks, and strict OpenSpec validation.
+- [x] 2.1 Add interaction tests for icon-only markup, accessibility semantics, validation gating, cancellation, and focus restoration compatibility.
+- [x] 2.2 Add style tests for compact baseline and cohesive-theme action sizing.
+- [x] 2.3 Run focused and complete foundation tests, Petclinic browser acceptance, syntax and formatting checks, and strict OpenSpec validation.
diff --git a/viewers/webcomponents/foundation/src/component-styles.css b/viewers/webcomponents/foundation/src/component-styles.css
index fe040b38013..688f7c98cd3 100644
--- a/viewers/webcomponents/foundation/src/component-styles.css
+++ b/viewers/webcomponents/foundation/src/component-styles.css
@@ -186,6 +186,25 @@ causeway-collection-column[hidden] {
flex-wrap: wrap;
gap: 0.5rem;
}
+.causeway-property-editor-action {
+ align-items: center;
+ block-size: 2rem;
+ box-sizing: border-box;
+ display: inline-flex;
+ inline-size: 2rem;
+ justify-content: center;
+ padding: 0.35rem;
+}
+.causeway-property-editor-action-icon {
+ block-size: 1rem;
+ fill: none;
+ inline-size: 1rem;
+ pointer-events: none;
+ stroke: currentColor;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-width: 2;
+}
.causeway-property-editor input:not([type="checkbox"]),
.causeway-property-editor select,
.causeway-property-editor textarea,
diff --git a/viewers/webcomponents/foundation/src/component-styles.mjs b/viewers/webcomponents/foundation/src/component-styles.mjs
index 611b2683fac..95607034d8d 100644
--- a/viewers/webcomponents/foundation/src/component-styles.mjs
+++ b/viewers/webcomponents/foundation/src/component-styles.mjs
@@ -187,6 +187,25 @@ causeway-collection-column[hidden] {
flex-wrap: wrap;
gap: 0.5rem;
}
+.causeway-property-editor-action {
+ align-items: center;
+ block-size: 2rem;
+ box-sizing: border-box;
+ display: inline-flex;
+ inline-size: 2rem;
+ justify-content: center;
+ padding: 0.35rem;
+}
+.causeway-property-editor-action-icon {
+ block-size: 1rem;
+ fill: none;
+ inline-size: 1rem;
+ pointer-events: none;
+ stroke: currentColor;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-width: 2;
+}
.causeway-property-editor input:not([type="checkbox"]),
.causeway-property-editor select,
.causeway-property-editor textarea,
diff --git a/viewers/webcomponents/foundation/src/property-element.mjs b/viewers/webcomponents/foundation/src/property-element.mjs
index 527c628e0b0..19e2a79b754 100644
--- a/viewers/webcomponents/foundation/src/property-element.mjs
+++ b/viewers/webcomponents/foundation/src/property-element.mjs
@@ -618,6 +618,8 @@ export class CausewayPropertyElement extends CausewayContextConsumerElement {
const statusMarkup = statusLabel
? `<span class="causeway-property-interaction-status" role="status">${escapeHtml(statusLabel)}</span>`
: '';
+ const saveLabel = `Save ${presentation.label}`;
+ const cancelLabel = `Cancel editing ${presentation.label}`;
this.setAttribute('data-editor', renderedEditor.editorId);
this.renderingInteraction = true;
try {
@@ -627,8 +629,8 @@ export class CausewayPropertyElement extends CausewayContextConsumerElement {
<div class="causeway-property-editor">${renderedEditor.html}</div>
${errorMarkup}
<div class="causeway-property-editor-actions">
- <button type="button" data-causeway-action="save"${this.#testId('save')} aria-disabled="${busy || Boolean(interaction.error)}">Save</button>
- <button type="button" data-causeway-action="cancel"${this.#testId('cancel')} ${interaction.status === InteractionStatus.SAVING ? 'disabled' : ''}>Cancel</button>
+ <button type="button" class="causeway-property-editor-action causeway-property-editor-save" data-causeway-action="save" aria-label="${escapeHtml(saveLabel)}" title="${escapeHtml(saveLabel)}"${this.#testId('save')} aria-disabled="${busy || Boolean(interaction.error)}"><svg class="causeway-property-editor-action-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12 4 4L19 6"></path></svg></button>
+ <button type="button" class="causeway-property-editor-action causeway-property-editor-cancel" data-causeway-action="cancel" aria-label="${escapeHtml(cancelLabel)}" title="${escapeHtml(cancelLabel)}"${this.#testId('cancel')} ${interaction.status === InteractionStatus.SAVING ? 'disabled' : ''}><svg class="causeway-property-editor-action-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12"></path><path d="M18 6 6 18"></path></svg></button>
</div>
${statusMarkup}
</div>`);
diff --git a/viewers/webcomponents/foundation/src/theme.css b/viewers/webcomponents/foundation/src/theme.css
index 5164cce863d..0857776c2ed 100644
--- a/viewers/webcomponents/foundation/src/theme.css
+++ b/viewers/webcomponents/foundation/src/theme.css
@@ -454,6 +454,28 @@ causeway-property:last-child .causeway-property {
gap: var(--causeway-space-2);
}
+.causeway-property-editor-action {
+ align-items: center;
+ block-size: 2rem;
+ box-sizing: border-box;
+ display: inline-flex;
+ inline-size: 2rem;
+ justify-content: center;
+ min-height: 0;
+ padding: 0.35rem;
+}
+
+.causeway-property-editor-action-icon {
+ block-size: 1rem;
+ fill: none;
+ inline-size: 1rem;
+ pointer-events: none;
+ stroke: currentColor;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-width: 2;
+}
+
.causeway-property-validation,
.causeway-action-prompt-error,
.causeway-error {
diff --git a/viewers/webcomponents/foundation/test/component-styles.test.mjs b/viewers/webcomponents/foundation/test/component-styles.test.mjs
index 3095546212b..968527843de 100644
--- a/viewers/webcomponents/foundation/test/component-styles.test.mjs
+++ b/viewers/webcomponents/foundation/test/component-styles.test.mjs
@@ -37,6 +37,15 @@ test('property edit affordance remains a compact deterministic icon control', as
assert.match(theme, /\.causeway-property-edit-icon \{[\s\S]*?block-size: 1rem;[\s\S]*?inline-size: 1rem;[\s\S]*?stroke: currentColor;/);
});
+test('property editor actions remain compact deterministic icon controls', async () => {
+ assert.match(CAUSEWAY_COMPONENT_STYLES, /\.causeway-property-editor-action \{[\s\S]*?block-size: 2rem;[\s\S]*?inline-size: 2rem;[\s\S]*?padding: 0\.35rem;/);
+ assert.match(CAUSEWAY_COMPONENT_STYLES, /\.causeway-property-editor-action-icon \{[\s\S]*?block-size: 1rem;[\s\S]*?inline-size: 1rem;[\s\S]*?stroke: currentColor;/);
+ const theme = await readFile(new URL('../src/theme.css', import.meta.url), 'utf8');
+ assert.match(theme, /\.causeway-property-editor-action \{[\s\S]*?block-size: 2rem;[\s\S]*?inline-size: 2rem;[\s\S]*?min-height: 0;/);
+ assert.match(theme, /\.causeway-property-editor-action-icon \{[\s\S]*?block-size: 1rem;[\s\S]*?inline-size: 1rem;[\s\S]*?stroke: currentColor;/);
+ assert.match(theme, /\.causeway-property-editor-actions button:first-child[\s\S]*?background: var\(--causeway-action-background\)/);
+});
+
test('direct member associations wrap in semantic source and keyboard order', () => {
assert.match(CAUSEWAY_COMPONENT_STYLES, /causeway-property\[data-causeway-action-group\]/);
assert.match(CAUSEWAY_COMPONENT_STYLES, /causeway-collection\[data-causeway-action-group\]/);
diff --git a/viewers/webcomponents/foundation/test/interaction.test.mjs b/viewers/webcomponents/foundation/test/interaction.test.mjs
index 92becb3d390..9705b2e094e 100644
--- a/viewers/webcomponents/foundation/test/interaction.test.mjs
+++ b/viewers/webcomponents/foundation/test/interaction.test.mjs
@@ -374,6 +374,10 @@ test('editable properties support prepare, validation, cancel and authoritative
assert.match(property.innerHTML, /data-control="text-area"[^>]+data-rows="5"/);
assert.doesNotMatch(property.innerHTML, /causeway-property-interaction-status/);
assert.doesNotMatch(property.innerHTML, />Editing</);
+ assert.match(property.innerHTML, /data-causeway-action="save"[^>]+aria-label="Save Name"[^>]+title="Save Name"/);
+ assert.match(property.innerHTML, /data-causeway-action="cancel"[^>]+aria-label="Cancel editing Name"[^>]+title="Cancel editing Name"/);
+ assert.equal([...property.innerHTML.matchAll(/<svg class="causeway-property-editor-action-icon"[^>]+aria-hidden="true"[^>]+focusable="false"/g)].length, 2);
+ assert.doesNotMatch(property.innerHTML, />(Save|Cancel)</);
property.setPendingValue('Deferred');
const pendingValidation = property.validatePending();
const editorTarget = {