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 = {