{
  "id": "TB02",
  "title": "Actions only",
  "topic": "toolbar",
  "use": "On a detail page the toolbar carries actions only. There is no search, no filter and no view switch — there is only this one record. The bar starts at the left and stays there, without a spacer. It sticks above the form, so that “Save voucher” does not scroll away on a long form.",
  "decisions": [
    "A detail page's toolbar carries actions only: with a single record there is nothing to search, filter or switch.",
    "The bar starts at the left and stays there: with no view controls there is no second group to hold at the right edge.",
    "The voucher's name, number and state stand in the page header, not in the bar, so the bar carries actions and nothing else on every detail page.",
    "The bar sticks above the form, so Save voucher stays in reach in the fortieth field; Save therefore stands only there, not again under the form.",
    "The sticky wrapper has an opaque background, the application's surface, or the content scrolls visibly through the bar.",
    "Two states, two badges: the voucher's processing state and the unsaved change are different things, and one badge for both would swallow one of them.",
    "Save voucher is disabled while nothing has changed, and an ed-tooltip on it says why; the header shows the change as the Unsaved badge, not the reason in words. The tooltip stands outside the bar, since in the action slot it would count as an action."
  ],
  "try": [
    {
      "do": "Point at Save voucher",
      "then": "the button is disabled, and its tooltip says why — “Nothing has changed”"
    },
    {
      "do": "Change a field",
      "then": "Save voucher becomes available, and the Unsaved badge appears in the header"
    },
    {
      "do": "Type the old value back in",
      "then": "the badge goes away, and the button is disabled again"
    },
    {
      "do": "Type the amount as 4812.5 or 4,812.50",
      "then": "it is the amount the voucher had, so the badge stays away and the button stays disabled"
    },
    {
      "do": "Press Review voucher",
      "then": "the state becomes Reviewed; an unsaved change stays beside it"
    },
    {
      "do": "Press Save voucher",
      "then": "the toast confirms with the voucher number, Unsaved disappears, the state stays"
    },
    {
      "do": "Make the window narrow",
      "then": "the toolbar stays at the left and does not move to the right"
    },
    {
      "do": "Scroll down",
      "then": "the toolbar stays at the top, the header scrolls away"
    }
  ],
  "revision": 3,
  "changes": [
    {
      "revision": 3,
      "change": "Amount is a text field with inputmode decimal instead of the number type, and the page reads it with the locale's separators: 4,812.50 typed over 4812.50 is the amount it had."
    },
    {
      "revision": 2,
      "change": "Why Save voucher is disabled is an ed-tooltip on the button, outside the toolbar; the header no longer says Nothing has changed, and the button has no title."
    }
  ],
  "reviewed": true,
  "source": "<style>\n  .tb02-toolbar {\n    position: sticky;\n    inset-block-start: 0;\n    z-index: 4;\n    background: var(--ed-color-surface-default);\n  }\n\n  .tb02-form {\n    max-inline-size: 56rem;\n  }\n</style>\n\n<div class=\"ed-stack ed-gap-4\">\n  <div class=\"ed-cluster ed-gap-3 ed-align-items-baseline\">\n    <span class=\"ed-caption-m ed-text-tabular\">RE-2026-04188</span>\n    <h2>Maintenance of lifting platform, hall 2</h2>\n    <div class=\"ed-cluster ed-gap-2 ed-align-items-baseline\" role=\"status\">\n      <ed-badge id=\"tb02-state\" variant=\"neutral\">Open</ed-badge>\n      <ed-badge id=\"tb02-unsaved\" variant=\"warning\" hidden>Unsaved</ed-badge>\n    </div>\n  </div>\n\n  <div class=\"tb02-toolbar ed-py-2\">\n    <ed-toolbar label=\"Voucher RE-2026-04188\">\n      <ed-button id=\"tb02-save\" disabled><ed-icon slot=\"start\" name=\"save\"></ed-icon>Save voucher</ed-button>\n      <ed-button id=\"tb02-review\" appearance=\"plain\"><ed-icon slot=\"start\" name=\"check\"></ed-icon>Review voucher</ed-button>\n      <ed-button appearance=\"plain\"><ed-icon slot=\"start\" name=\"copy\"></ed-icon>Copy voucher</ed-button>\n      <ed-divider orientation=\"vertical\"></ed-divider>\n      <ed-button appearance=\"plain\"><ed-icon slot=\"start\" name=\"delete\"></ed-icon>Delete voucher</ed-button>\n    </ed-toolbar>\n    <ed-tooltip id=\"tb02-save-reason\" for=\"tb02-save\">Nothing has changed</ed-tooltip>\n  </div>\n\n  <form id=\"tb02-form\" class=\"tb02-form ed-stack ed-gap-8\">\n    <section class=\"ed-stack ed-gap-4\">\n      <h3>Voucher</h3>\n      <p>Voucher number, currency and client are fixed with the voucher and cannot be changed when posting.</p>\n      <div class=\"ed-grid\" style=\"--ed-grid-min-column-size: 20rem\">\n        <ed-input name=\"voucher-number\" label=\"Voucher number\" value=\"RE-2026-04188\" readonly></ed-input>\n        <ed-input name=\"posting-text\" label=\"Posting text\" value=\"Maintenance of lifting platform, hall 2\" required></ed-input>\n      </div>\n      <div class=\"ed-grid-fill\" style=\"--ed-grid-min-column-size: 12rem\">\n        <ed-input name=\"voucher-date\" label=\"Voucher date\" value=\"08/04/2026\" required></ed-input>\n        <ed-select name=\"voucher-type\" label=\"Voucher type\" value=\"outgoing-invoice\">\n          <ed-option value=\"outgoing-invoice\">Outgoing invoice</ed-option>\n          <ed-option value=\"incoming-invoice\">Incoming invoice</ed-option>\n          <ed-option value=\"cash-receipt\">Cash receipt</ed-option>\n        </ed-select>\n      </div>\n    </section>\n\n    <section class=\"ed-stack ed-gap-4\">\n      <h3>Posting</h3>\n      <div class=\"ed-grid-fill\" style=\"--ed-grid-min-column-size: 12rem\">\n        <ed-input name=\"account\" label=\"Account\" value=\"8400\" required></ed-input>\n        <ed-input name=\"contra-account\" label=\"Contra account\" value=\"10000\" required></ed-input>\n        <ed-input name=\"cost-center\" label=\"Cost center\" value=\"4711\"></ed-input>\n        <ed-input name=\"amount\" label=\"Amount\" inputmode=\"decimal\" value=\"4812.50\" required></ed-input>\n        <ed-input name=\"vat\" label=\"VAT in %\" value=\"19\"></ed-input>\n        <ed-input name=\"currency\" label=\"Currency\" value=\"EUR\" readonly></ed-input>\n      </div>\n    </section>\n\n    <section class=\"ed-stack ed-gap-4\">\n      <h3>Assignment</h3>\n      <div class=\"ed-grid\" style=\"--ed-grid-min-column-size: 20rem\">\n        <ed-input name=\"client\" label=\"Client\" value=\"Steinbach-Wallner GmbH\" readonly></ed-input>\n        <ed-input name=\"clerk\" label=\"Clerk\" value=\"Sophie Brandt\"></ed-input>\n      </div>\n      <ed-textarea name=\"note\" label=\"Note\" rows=\"3\" value=\"Maintenance contract runs until December 31, 2026.\"></ed-textarea>\n    </section>\n  </form>\n</div>\n\n<ed-toast-stack id=\"tb02-toasts\"></ed-toast-stack>\n\n<script type=\"module\">\n  const form = document.getElementById('tb02-form');\n  const save = document.getElementById('tb02-save');\n  const review = document.getElementById('tb02-review');\n  const state = document.getElementById('tb02-state');\n  const unsaved = document.getElementById('tb02-unsaved');\n  const saveReason = document.getElementById('tb02-save-reason');\n  const toasts = document.getElementById('tb02-toasts');\n  const fields = [...form.querySelectorAll('[name]')];\n  const saved = new Map();\n\n  // An amount is text the product reads in the page's locale: 4812.5 or 4,812.50 typed over 4812.50 is\n  // the amount it had. The locale's own separators decide, so a German page reads 4.812,50 the same way.\n  const formatFor = locale => {\n    try {\n      return new Intl.NumberFormat(locale);\n    } catch {\n      return new Intl.NumberFormat('en');\n    }\n  };\n  const parts = formatFor(document.documentElement.lang || navigator.language).formatToParts(12345.6);\n  const groupSeparator = parts.find(part => part.type === 'group')?.value ?? '';\n  const decimalSeparator = parts.find(part => part.type === 'decimal')?.value ?? '.';\n  const amountOf = text => {\n    const plain = text.trim().replaceAll(groupSeparator, '').replace(decimalSeparator, '.');\n    return /^-?(\\d+\\.?\\d*|\\.\\d+)$/.test(plain) ? Number(plain) : NaN;\n  };\n  const same = (field, before) =>\n    field.value === before ||\n    (field.inputmode === 'decimal' && !Number.isNaN(amountOf(field.value)) && amountOf(field.value) === amountOf(before));\n\n  const update = () => {\n    const changed = fields.some(field => !same(field, saved.get(field)));\n    unsaved.hidden = !changed;\n    save.disabled = !changed;\n    saveReason.disabled = changed;\n  };\n\n  await Promise.all(['ed-input', 'ed-select', 'ed-option', 'ed-textarea'].map(tag => customElements.whenDefined(tag)));\n  await Promise.all(fields.map(field => field.updateComplete));\n  fields.forEach(field => saved.set(field, field.value));\n\n  form.addEventListener('input', update);\n  form.addEventListener('change', update);\n\n  save.addEventListener('click', () => {\n    fields.forEach(field => saved.set(field, field.value));\n    update();\n    toasts.create({ variant: 'success', title: 'Voucher RE-2026-04188 saved' });\n  });\n\n  review.addEventListener('click', () => {\n    state.textContent = 'Reviewed';\n    state.variant = 'info';\n  });\n</script>",
  "page": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/toolbar/tb02-actions-only/",
  "topicPage": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/toolbar/"
}
