Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 13 additions & 7 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions docs/ReadOnlyMode.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Read-only mode disables editor-owned mutations:

When `readOnly` changes from `false` to `true`, `CascadeEditor` closes active slash sessions, cancels active drags, clears block selection, and dismisses editor-owned link editing popup sessions. This cleanup does not change document blocks, text focus, caret, or text selection.

The edit-oriented empty-document placeholder is also suppressed in read-only mode, even when `emptyDocumentPlaceholderEnabled` is `true`.

## Toolbar

Read-only mode does not add a separate toolbar visibility switch.
Expand Down
15 changes: 11 additions & 4 deletions docs/ThemingAndLocalization.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 1. Feature Overview

This feature introduces a **theme + strings** configuration layer that lets consumers control every visual and textual aspect of the editor via three top-level parameters on `CascadeEditor()`. All hardcoded `Color(0x...)` values, font sizes, and UI strings have been replaced with reads from `CompositionLocal`-provided config objects. The editor ships light and dark presets and English defaults, while remaining fully open to custom palettes, typography, and translations. Existing consumers are unaffected — all new parameters have defaults that reproduce the previous behavior exactly.
This feature introduces a **theme + strings** configuration layer that lets consumers control every visual and textual aspect of the editor via three top-level parameters on `CascadeEditor()`. All hardcoded `Color(0x...)` values, font sizes, and UI strings have been replaced with reads from `CompositionLocal`-provided config objects. The editor ships light and dark presets and English defaults, while remaining fully open to custom palettes, typography, and translations. The empty-document hint is available as an opt-in behavior.

---

Expand All @@ -12,10 +12,10 @@ This feature introduces a **theme + strings** configuration layer that lets cons

| Class | Role |
|-------|------|
| `CascadeEditorColors` | `@Immutable data class` — 21 named color slots covering every hardcoded color in the editor plus rich-text rendering colors. Ships `light()` and `dark()` presets. |
| `CascadeEditorColors` | `@Immutable data class` — 25 named color slots covering every editor visual, including the empty-document placeholder and rich-text rendering colors. Ships `light()` and `dark()` presets. |
| `CascadeEditorTypography` | `@Immutable data class` — 11 `TextStyle` slots (body, heading1–6, code, slash UI, toolbar). Ships `default()`. Styles carry font properties only — **no color**. |
| `CascadeEditorTheme` | Facade combining `colors` + `typography`. Ships `light()` / `dark()`. |
| `CascadeEditorStrings` | `@Immutable data class` — UI chrome strings: toolbar accessibility labels, link popup labels, back button text, and error-message lambdas. Ships `default()` (English). |
| `CascadeEditorStrings` | `@Immutable data class` — UI chrome strings: toolbar accessibility labels, link popup labels, the empty-document placeholder, back button text, and error-message lambdas. Ships `default()` (English). |
| `BlockLocalizedStrings` | Per-block-type display name, description, and additive search keywords. |
| `CascadeEditorBlockStrings` | `Map<typeId, BlockLocalizedStrings>` with a `forType()` lookup. Ships `default()` (English, mirrors `BlockRegistry`). |
| `LocalCascadeTheme` | `internal staticCompositionLocalOf<CascadeEditorTheme>` |
Expand Down Expand Up @@ -62,6 +62,7 @@ Consumer
├─ CompositionLocalProvider(LocalCascadeStrings provides strings, ...)
│ ├─ RichTextToolbar: localizedLabel(spec, strings) for accessibility
│ ├─ SlashCommandPopup: strings.back for back button
│ ├─ TextBlockField: strings.emptyDocumentPlaceholder for an empty editable document
│ └─ UnknownBlockRenderer: strings.unsupportedBlock(typeId)
└─ remember(blockStrings) {
Expand Down Expand Up @@ -144,6 +145,9 @@ CascadeEditor(
```kotlin
// Provide French strings
CascadeEditor(
config = CascadeEditorConfig(
emptyDocumentPlaceholderEnabled = true,
),
strings = CascadeEditorStrings(
back = "\u2039 Retour",
unsupportedBlock = { "Type de bloc non supporté : $it" },
Expand All @@ -164,6 +168,7 @@ CascadeEditor(
linkTitle = "Titre",
linkUrl = "URL",
linkValidationError = { "URL invalide : $it" },
emptyDocumentPlaceholder = "Commencez ici…",
),
blockStrings = CascadeEditorBlockStrings(
blocks = mapOf(
Expand Down Expand Up @@ -202,7 +207,7 @@ Theme/string `CompositionLocal` instances (`LocalCascadeTheme`, `LocalCascadeStr
| `ui/DropIndicator.kt` | Resolves `colors.primary` from theme when no color override; removed `DropIndicatorDefaults.Color` |
| `ui/BackspaceAwareTextEdit.kt` | Added `cursorBrush` parameter |
| `ui/renderers/TextBlockRenderer.kt` | Reads typography for heading/body/code styles; applies `colors.text` |
| `ui/renderers/TextBlockField.kt` | Captures `colors.linkText`/`colors.inlineCodeBackground`/`colors.highlight`, passes them to SpanMapper via OutputTransformation |
| `ui/renderers/TextBlockField.kt` | Renders the localized/themed empty-document placeholder beneath the real field; captures `colors.linkText`/`colors.inlineCodeBackground`/`colors.highlight` for SpanMapper |
| `ui/renderers/TodoBlockRenderer.kt` | Reads theme for body typography + text color |
| `ui/renderers/DividerBlockRenderer.kt` | Reads `colors.contentDivider` |
| `ui/renderers/UnknownBlockRenderer.kt` | Reads `colors` + `strings` for background/text/error message |
Expand All @@ -228,6 +233,8 @@ Theme/string `CompositionLocal` instances (`LocalCascadeTheme`, `LocalCascadeStr

**String lambda equality.** `CascadeEditorStrings` is a data class, but lambdas don't participate in structural equality. Default lambdas such as `unsupportedBlock` and `linkValidationError` are extracted as singletons to ensure `CascadeEditorStrings.default() == CascadeEditorStrings.default()` holds true.

**Placeholder is presentation, not content.** `emptyDocumentPlaceholder` is drawn beneath the existing text field only for a sole empty root paragraph or heading. Visibility uses the live text field's sentinel-aware empty check, so the ZWSP backspace sentinel never blocks the hint and the hint never enters blocks, serialization, history, selection, or preview mode. `CascadeEditorConfig.emptyDocumentPlaceholderEnabled` defaults to `false`; read-only mode suppresses the edit-oriented hint when explicitly enabled.

**Typography excludes color.** `TextStyle` slots in `CascadeEditorTypography` carry size, weight, and font family only. Color is always applied from `CascadeEditorColors` at use-site via `.copy(color = colors.text)`. Providing a `TextStyle` with color set will have that color overridden.

**`UnknownBlockRenderer` font size stays hardcoded.** It renders `14.sp` for error text — intentional, as it's a debug/error display not considered user-facing content typography.
Expand Down
8 changes: 4 additions & 4 deletions docs/iOsNativeSdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ The built-in slash executor also distinguishes text and non-text conversion targ
### SDK identity and configuration

- `CascadeEditorSdk.version: String` — currently `"1.0.0"`.
- `CascadeEditorConfiguration` — immutable configuration containing `readOnly`, `toolbarMode`, `slashCommandsEnabled`, `blockSelectionEnabled`, `blockDraggingEnabled`, `blockIndentationEnabled`, `isDark`, and `crashPolicy`. Setting `blockIndentationEnabled` to `false` disables indentation commands and keeps block drag/reorder at the payload's original indentation depth.
- `CascadeEditorConfiguration` — immutable configuration containing `readOnly`, `toolbarMode`, `slashCommandsEnabled`, `blockSelectionEnabled`, `blockDraggingEnabled`, `blockIndentationEnabled`, `emptyDocumentPlaceholderEnabled`, `isDark`, and `crashPolicy`. The placeholder flag defaults to `false`; when enabled, read-only mode still suppresses the edit-oriented hint.
- `CascadeToolbarMode` — `builtIn` or `none`; `none` disables both the built-in formatting toolbar and its link popup.
- `CascadeCrashPolicy` — `containAndReport` or `rethrow`.

Expand Down Expand Up @@ -224,8 +224,8 @@ Runtime configuration, colors, and localization:
- `updateConfiguration(value)`, `setReadOnly(value)`, `setDarkMode(value)`, `setToolbarMode(value)`, `setSlashCommandsEnabled(value)`
- `setColors(CascadeEditorColors)`, `clearCustomColors()`
- `setLocalization(CascadeEditorLocalization)`
- `CascadeEditorColors` is a mutable, complete 24-slot palette seeded from the built-in light (`init()`) or light/dark (`init(isDark:)`) preset. Every property is a Swift `Int64` containing `0xAARRGGBB`.
- `CascadeLocalizedStrings` supplies nullable overrides for built-in UI/accessibility strings.
- `CascadeEditorColors` is a mutable, complete 25-slot palette seeded from the built-in light (`init()`) or light/dark (`init(isDark:)`) preset. Every property is a Swift `Int64` containing `0xAARRGGBB`; `placeholderText` controls the empty-document hint.
- `CascadeLocalizedStrings` supplies nullable overrides for built-in UI/accessibility strings, including `emptyDocumentPlaceholder`.
- `CascadeLocalizedBlockStrings` supplies slash-menu name, description, and additive keywords by block type ID.

`setColors` snapshots the whole bag and can be called before or after
Expand Down Expand Up @@ -368,7 +368,7 @@ Back-button flush keeps the editor open and surfaces the save failure.
- **Registry system:** native blocks extend `BlockRegistry`; native commands extend `SlashCommandRegistry`. Observable revisions make runtime registration visible to an already-mounted editor.
- **Serialization:** editable JSON uses `DocumentSchema` plus `NativeCustomBlockCodec`; HTML uses `HtmlProfile.Default`. Both share the editor's live text/span holders. The preview facade uses `DocumentSchema` directly to preserve generic unknown/custom data without creating runtime holders or resolving native editor types.
- **Rich text and toolbar:** the bridge maps `FormattingState`, `IndentationState`, and `LinkState` from `CascadeEditorToolbarController` into Swift-friendly state and actions.
- **Localization and theme:** Swift string overrides resolve into core `CascadeEditorStrings` and `CascadeEditorBlockStrings`. `CascadeEditorColors` snapshots map all 24 ARGB slots into the core palette and recompose live. Without a custom palette, `isDark` selects the built-in colors; it remains exposed to native custom blocks in either mode.
- **Localization and theme:** Swift string overrides resolve into core `CascadeEditorStrings` and `CascadeEditorBlockStrings`. `CascadeEditorColors` snapshots map all 25 ARGB slots into the core palette and recompose live. Without a custom palette, `isDark` selects the built-in colors; it remains exposed to native custom blocks in either mode.
- **Local build:** `scripts/build-xcframework.sh` assembles the dynamic debug XCFramework at `editor-ios-sdk/build/XCFrameworks/debug/CascadeEditor.xcframework`. The Xcode sample links and embeds this local artifact.
- **External distribution:** `scripts/package-ios-sdk.sh` tests and assembles the release framework, validates resources/privacy/version/dSYMs, and emits a one-root ZIP plus SwiftPM checksum and manifest. `release-ios-sdk.yml` publishes immutable source-tag assets and updates `linreal/cascade-editor-ios`.
- **Consumer validation:** `scripts/validate-ios-consumer.sh` stages the publication ZIP in `iosConsumerSmokeTest`, builds a generic-device Release app, executes its UI test on an arm64 simulator, and inspects the embedded framework.
Expand Down
72 changes: 72 additions & 0 deletions docs/recipes/EmptyDocumentPlaceholder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Empty Document Placeholder

Use this recipe to show a hint such as `Start here…` in a new or empty
document. The feature is opt-in and appears only when the editable document
contains one empty root `Paragraph` or `Heading`. Read-only editors suppress it.

## Compose Multiplatform

Enable the behavior through `CascadeEditorConfig`. The built-in string and
theme color are used unless you override them:

```kotlin
CascadeEditor(
// existing state and registries
config = CascadeEditorConfig(
emptyDocumentPlaceholderEnabled = true,
),
strings = CascadeEditorStrings.default().copy(
emptyDocumentPlaceholder = "Add a title…",
),
theme = CascadeEditorTheme.light().let { theme ->
theme.copy(
colors = theme.colors.copy(
placeholderText = Color(0xFF74747B),
),
)
},
)
```

If the screen already owns a configuration, preserve its other settings with
`config.copy(emptyDocumentPlaceholderEnabled = true)`.

## Native iOS (Swift)

Pass `emptyDocumentPlaceholderEnabled: true` in the immutable native
configuration. Swift uses the complete primary initializer:

```swift
let configuration = CascadeEditorConfiguration(
readOnly: false,
toolbarMode: .builtIn,
slashCommandsEnabled: true,
blockSelectionEnabled: true,
blockDraggingEnabled: true,
isDark: false,
crashPolicy: .containAndReport,
blockIndentationEnabled: true,
emptyDocumentPlaceholderEnabled: true
)

let controller = CascadeEditorController(
initialJson: nil,
configuration: configuration
)

let strings = CascadeLocalizedStrings()
strings.emptyDocumentPlaceholder = "Add a title…"
controller.setLocalization(
localization: CascadeEditorLocalization(strings: strings)
)

let colors = CascadeEditorColors(isDark: false)
colors.placeholderText = 0xFF74747B
controller.setColors(colors: colors)
```

The localization and color calls are optional; omit them to use the built-in
`Start here…` string and the preset placeholder color. For an existing
controller, pass the updated full configuration to
`controller.updateConfiguration(value:)`.

6 changes: 3 additions & 3 deletions docs/recipes/ThemeCustomization.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Dark. If the app has its own appearance override, pass that Boolean instead.
## Native iOS (Swift/SwiftUI)

The native SDK accepts colors as `0xAARRGGBB` values in a mutable
`CascadeEditorColors` bag. Seed the bag first so it always contains all 24
`CascadeEditorColors` bag. Seed the bag first so it always contains all 25
slots:

```swift
Expand Down Expand Up @@ -147,15 +147,15 @@ variant change.

## Color-slot checklist

Both APIs expose the same 24 semantic slots:
Both APIs expose the same 25 semantic slots:

`primary`, `onPrimary`, `text`, `popupBackground`,
`unknownBlockBackground`, `toolbarIcon`, `toolbarIconDisabled`,
`slashItemTitle`, `slashChevron`, `unknownBlockText`, `uiDivider`,
`contentDivider`, `slashSelectedItem`, `inlineCodeBackground`, `highlight`,
`cursor`, `textSelectionBackground`, `quoteBorder`, `quoteBackground`,
`selectionOverlay`, `linkText`, `error`, `codeBlockBackground`, and
`toolbarBackground`.
`toolbarBackground`, and `placeholderText`.

Reference implementations:

Expand Down
Loading
Loading