import SwiftUI @MainActor struct EditorToolbar: View { @Bindable var model: DocsModel let content: NSAttributedString @State private var settledSelection = DocumentToolbarSelection() private var selection: DocumentToolbarSelection { currentSelection ?? settledSelection } private var currentSelection: DocumentToolbarSelection? { DocumentToolbarSelection(commands: actions) } var body: some View { let selection = self.selection ScrollView(.horizontal) { HStack(spacing: 12) { command("docs.editor.toolbar.undo", icon: "arrow.uturn.backward", op: "undo", enabled: selection.canUndo) command("docs.editor.toolbar.redo", icon: "arrow.uturn.forward", op: "redo", enabled: selection.canRedo) Divider().frame(height: 24) Menu { Button { block("paragraph") } label: { Text("docs.editor.toolbar.block.paragraph", bundle: DocsResources.bundle) } ForEach(1...3, id: \.self) { level in Button { block("heading", level: level) } label: { Text(LocalizedStringKey("docs.editor.toolbar.block.heading\(level)"), bundle: DocsResources.bundle) } } } label: { Text(selection.styleLabel).frame(minWidth: 100, alignment: .leading) }.accessibilityLabel(Text("native.style", bundle: DocsResources.bundle)) Menu { ForEach(["sans", "serif", "mono"], id: \.self) { family in Button { format("font_" + family, exclusive: ["font_sans", "font_serif", "font_mono"]) } label: { Text(LocalizedStringKey("docs.editor.toolbar.font." + family), bundle: DocsResources.bundle) } } } label: { Text(LocalizedStringKey("docs.editor.toolbar.font." + selection.font), bundle: DocsResources.bundle).frame(minWidth: 80, alignment: .leading) }.accessibilityLabel(Text("native.font", bundle: DocsResources.bundle)) Menu { ForEach([12, 8, 9, 10, 11, 14, 18, 24, 36, 48, 72], id: \.self) { size in Button(size.formatted()) { format("size_\(size)", exclusive: [8,9,10,11,12,14,18,24,36,48,72].map { "size_\($0)" } + ["size_sm", "size_lg", "size_xl"]) } } } label: { Text(selection.size.formatted()).frame(minWidth: 32) }.accessibilityLabel(Text("native.size", bundle: DocsResources.bundle)) Divider().frame(height: 24) mark("docs.editor.toolbar.bold", icon: "bold", name: "strong", active: selection.marks.contains("strong")) mark("docs.editor.toolbar.italic", icon: "italic", name: "em", active: selection.marks.contains("em")) mark("docs.editor.toolbar.code", icon: "chevron.left.forwardslash.chevron.right", name: "code", active: selection.marks.contains("code")) Divider().frame(height: 24) ForEach(["left", "center", "right"], id: \.self) { alignment in Button { blockAttribute("align", value: alignment) } label: { Image(systemName: "text.align" + alignment) } .accessibilityLabel(Text(LocalizedStringKey("docs.editor.toolbar.align." + alignment), bundle: DocsResources.bundle)) .foregroundStyle(selection.alignment == alignment ? Color.accentColor : Color.primary) .accessibilityAddTraits(selection.alignment == alignment ? .isSelected : []) } Divider().frame(height: 24) ForEach(["bullet_list", "ordered_list", "blockquote"], id: \.self) { type in Button { block(type) } label: { Image(systemName: type == "bullet_list" ? "list.bullet" : type == "ordered_list" ? "list.number" : "text.quote") } .accessibilityLabel(Text(LocalizedStringKey("native." + type), bundle: DocsResources.bundle)) .foregroundStyle(selection.blocks.contains(type) ? Color.accentColor : Color.primary) .accessibilityAddTraits(selection.blocks.contains(type) ? .isSelected : []) } command("docs.editor.menu.format.indent", icon: "increase.indent", op: "indent", enabled: true) command("docs.editor.menu.format.outdent", icon: "decrease.indent", op: "outdent", enabled: true) command("docs.editor.menu.format.clear", icon: "eraser", op: "clear-format", enabled: selection.hasSelection) }.buttonStyle(.borderless).padding(.horizontal, 12).padding(.vertical, 8) } .scrollIndicators(.hidden) .fixedSize(horizontal: false, vertical: true) .accessibilityIdentifier("docs.formatting-toolbar") // Replica work briefly gates commands on every keystroke. Keep that // synchronization gate out of disabled styling so the bar does not blink. .disabled(!actions.isAvailable) .allowsHitTesting(actions.isEnabled) .onChange(of: currentSelection, initial: true) { _, value in if let value { settledSelection = value } } } private func command(_ key: String, icon: String, op: String, enabled: Bool) -> some View { Button { actions.perform(op) } label: { Image(systemName: icon) } .accessibilityLabel(Text(LocalizedStringKey(key), bundle: DocsResources.bundle)).disabled(!enabled) } private func mark(_ key: String, icon: String, name: String, active: Bool) -> some View { Button { format(name) } label: { Image(systemName: icon) }.accessibilityLabel(Text(LocalizedStringKey(key), bundle: DocsResources.bundle)) .foregroundStyle(active ? Color.accentColor : Color.primary) .accessibilityAddTraits(active ? .isSelected : []) } private var actions: DocumentCommands { DocumentCommands(model: model, content: content) } private func format(_ name: String, exclusive: [String] = []) { actions.format(name, exclusive: exclusive) } private func block(_ type: String, level: Int = 1) { actions.block(type, level: level) } private func blockAttribute(_ name: String, value: String) { actions.blockAttribute(name, value: value == "left" ? nil : value) } }