diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx
index 16206db25..3c30c24e1 100644
--- a/docs/src/content/docs/changelog.mdx
+++ b/docs/src/content/docs/changelog.mdx
@@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
*/
## [Unreleased]
+## v3.0.0-alpha.73 - 2026-02-27
+
+## Fixed
+- Fix frameless window cannot be minimized on darwin (#4294)
+
## v3.0.0-alpha.72 - 2026-02-16
## Fixed
diff --git a/docs/src/content/docs/concepts/architecture.mdx b/docs/src/content/docs/concepts/architecture.mdx
index d72affe3b..6b81a15a8 100644
--- a/docs/src/content/docs/concepts/architecture.mdx
+++ b/docs/src/content/docs/concepts/architecture.mdx
@@ -269,9 +269,9 @@ app.Event.Emit("user-logged-in", user)
```javascript
// JavaScript: Listen for event
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-On('user-logged-in', (user) => {
+Events.On('user-logged-in', (user) => {
console.log('User logged in:', user)
})
```
diff --git a/docs/src/content/docs/concepts/bridge.mdx b/docs/src/content/docs/concepts/bridge.mdx
index 7593d896a..13a8ac4e8 100644
--- a/docs/src/content/docs/concepts/bridge.mdx
+++ b/docs/src/content/docs/concepts/bridge.mdx
@@ -549,11 +549,11 @@ func ProcessLargeFile(path string) error {
```
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
import { ProcessLargeFile } from './bindings/FileService'
// Listen for progress
-On('file-progress', (data) => {
+Events.On('file-progress', (data) => {
console.log(`Line ${data.line}: ${data.text}`)
})
diff --git a/docs/src/content/docs/contributing/binding-system.mdx b/docs/src/content/docs/contributing/binding-system.mdx
index 63f672155..d8609b748 100644
--- a/docs/src/content/docs/contributing/binding-system.mdx
+++ b/docs/src/content/docs/contributing/binding-system.mdx
@@ -139,9 +139,9 @@ export namespace backend {
### JavaScript Side
```ts
-import { invoke } from "@wailsio/runtime";
+import { System } from "@wailsio/runtime";
-await invoke(0x7a1201d3 /* ChatService.Send */, ["Hello"]);
+await System.invoke(0x7a1201d3 /* ChatService.Send */, ["Hello"]);
```
Implementation: `runtime/desktop/@wailsio/runtime/invoke.ts`
diff --git a/docs/src/content/docs/features/dialogs/custom.mdx b/docs/src/content/docs/features/dialogs/custom.mdx
index 591d68db2..242527465 100644
--- a/docs/src/content/docs/features/dialogs/custom.mdx
+++ b/docs/src/content/docs/features/dialogs/custom.mdx
@@ -199,14 +199,14 @@ func ShowConfirmdialog(message string) bool {
```
@@ -389,17 +389,17 @@ func (ld *Logindialog) Cancel() {
```
diff --git a/docs/src/content/docs/features/events/system.mdx b/docs/src/content/docs/features/events/system.mdx
index 678abff2f..6a4c49725 100644
--- a/docs/src/content/docs/features/events/system.mdx
+++ b/docs/src/content/docs/features/events/system.mdx
@@ -129,13 +129,13 @@ window.EmitEvent("window-specific-event", data)
### From JavaScript
```javascript
-import { Emit } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Emit to Go
-Emit("button-clicked", { buttonId: "submit" })
+Events.Emit("button-clicked", { buttonId: "submit" })
// Emit to all windows
-Emit("broadcast-message", "Hello everyone")
+Events.Emit("broadcast-message", "Hello everyone")
```
## Listening to Events
@@ -506,7 +506,7 @@ func main() {
**JavaScript:**
```javascript
-import { Events, Emit } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Listen for notifications
Events.On("notification", (event) => {
diff --git a/docs/src/content/docs/features/windows/frameless.mdx b/docs/src/content/docs/features/windows/frameless.mdx
index 361cced18..b8075b92a 100644
--- a/docs/src/content/docs/features/windows/frameless.mdx
+++ b/docs/src/content/docs/features/windows/frameless.mdx
@@ -166,11 +166,11 @@ Use the `--wails-draggable` CSS property:
**JavaScript for buttons:**
```javascript
-import { WindowMinimise, WindowMaximise, WindowClose } from '@wailsio/runtime'
+import { Window } from '@wailsio/runtime'
-document.querySelector('.minimize').addEventListener('click', WindowMinimise)
-document.querySelector('.maximize').addEventListener('click', WindowMaximise)
-document.querySelector('.close').addEventListener('click', WindowClose)
+document.querySelector('.minimize').addEventListener('click', () => Window.Minimise())
+document.querySelector('.maximize').addEventListener('click', () => Window.Maximise())
+document.querySelector('.close').addEventListener('click', () => Window.Close())
```
## System Buttons
@@ -214,15 +214,11 @@ document.querySelector('.close').addEventListener('click', Close)
**Or use runtime methods:**
```javascript
-import {
- WindowMinimise,
- WindowMaximise,
- WindowClose
-} from '@wailsio/runtime'
+import { Window } from '@wailsio/runtime'
-document.querySelector('.minimize').addEventListener('click', WindowMinimise)
-document.querySelector('.maximize').addEventListener('click', WindowMaximise)
-document.querySelector('.close').addEventListener('click', WindowClose)
+document.querySelector('.minimize').addEventListener('click', () => Window.Minimise())
+document.querySelector('.maximize').addEventListener('click', () => Window.Maximise())
+document.querySelector('.close').addEventListener('click', () => Window.Close())
```
### Toggle Maximise State
@@ -230,22 +226,22 @@ document.querySelector('.close').addEventListener('click', WindowClose)
Track maximise state for button icon:
```javascript
-import { WindowIsMaximised, WindowMaximise, WindowUnMaximise } from '@wailsio/runtime'
+import { Window } from '@wailsio/runtime'
async function toggleMaximise() {
- const isMaximised = await WindowIsMaximised()
-
+ const isMaximised = await Window.IsMaximised()
+
if (isMaximised) {
- await WindowUnMaximise()
+ await Window.Restore()
} else {
- await WindowMaximise()
+ await Window.Maximise()
}
-
+
updateMaximiseButton()
}
async function updateMaximiseButton() {
- const isMaximised = await WindowIsMaximised()
+ const isMaximised = await Window.IsMaximised()
const button = document.querySelector('.maximize')
button.textContent = isMaximised ? '❐' : '□'
}
@@ -737,41 +733,35 @@ body {
**JavaScript:**
```javascript
-import {
- WindowMinimise,
- WindowMaximise,
- WindowUnMaximise,
- WindowIsMaximised,
- WindowClose
-} from '@wailsio/runtime'
+import { Window } from '@wailsio/runtime'
// Minimise button
document.querySelector('.minimize').addEventListener('click', () => {
- WindowMinimise()
+ Window.Minimise()
})
// Maximise/restore button
const maximiseBtn = document.querySelector('.maximize')
maximiseBtn.addEventListener('click', async () => {
- const isMaximised = await WindowIsMaximised()
-
+ const isMaximised = await Window.IsMaximised()
+
if (isMaximised) {
- await WindowUnMaximise()
+ await Window.Restore()
} else {
- await WindowMaximise()
+ await Window.Maximise()
}
-
+
updateMaximiseButton()
})
// Close button
document.querySelector('.close').addEventListener('click', () => {
- WindowClose()
+ Window.Close()
})
// Update maximise button icon
async function updateMaximiseButton() {
- const isMaximised = await WindowIsMaximised()
+ const isMaximised = await Window.IsMaximised()
maximiseBtn.textContent = isMaximised ? '❐' : '□'
maximiseBtn.title = isMaximised ? 'Restore' : 'Maximise'
}
diff --git a/docs/src/content/docs/features/windows/multiple.mdx b/docs/src/content/docs/features/windows/multiple.mdx
index 41d5a18fe..60694abc9 100644
--- a/docs/src/content/docs/features/windows/multiple.mdx
+++ b/docs/src/content/docs/features/windows/multiple.mdx
@@ -288,7 +288,7 @@ func CreateToolPalette(app *application.Application) *application.WebviewWindow
}
```
-### Pattern 4: Modal dialogs
+### Pattern 4: Modal dialogs (macOS only)
Child windows that block parent:
@@ -303,14 +303,7 @@ func ShowModaldialog(parent *application.WebviewWindow, title string) {
Resizable: false,
})
- // Disable parent (platform-specific)
- parent.SetEnabled(false)
-
- // Re-enable parent on close
- dialog.OnDestroy(func() {
- parent.SetEnabled(true)
- parent.SetFocus()
- })
+ parent.AttachModal(dialog)
}
```
@@ -359,51 +352,38 @@ func (e *EditorApp) TogglePreview() {
```go
childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Child Window",
- Parent: parentWindow,
})
+
+parentWindow.AttachModal(childWindow)
```
**Behaviour:**
-- Child closes when parent closes
-- Child stays above parent (on some platforms)
-- Child minimises with parent (on some platforms)
+- Child stays above parent
+- Child moves with parent
+- Child blocks interaction to parent
**Platform support:**
-| Feature | macOS | Windows | Linux |
-|---------|-------|---------|-------|
-| Auto-close | ✅ | ✅ | ⚠️ Varies |
-| Stay above | ✅ | ⚠️ Partial | ⚠️ Varies |
-| Minimise together | ✅ | ❌ | ⚠️ Varies |
+| macOS | Windows | Linux |
+|-------|---------|-------|
+| ✅ | ❌ | ❌ |
### Modal Behaviour
Create modal-like behaviour:
```go
-func ShowModal(parent *application.WebviewWindow) {
- modal := app.Window.NewWithOptions(application.WebviewWindowOptions{
- Title: "Modal dialog",
+func ShowModaldialog(parent *application.WebviewWindow, title string) {
+ dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
+ Title: title,
Width: 400,
Height: 200,
- Parent: parent,
- AlwaysOnTop: true,
})
- // Disable parent interaction
- parent.SetEnabled(false)
-
- // Re-enable on close
- modal.OnClose(func() bool {
- parent.SetEnabled(true)
- parent.SetFocus()
- return true
- })
+ parent.AttachModal(dialog)
}
```
-**Note:** True modal behaviour (blocking) varies by platform.
-
## Window Lifecycle Management
### Creation Callbacks
diff --git a/docs/src/content/docs/guides/events-reference.mdx b/docs/src/content/docs/guides/events-reference.mdx
index afa61fc3e..27308a17e 100644
--- a/docs/src/content/docs/guides/events-reference.mdx
+++ b/docs/src/content/docs/guides/events-reference.mdx
@@ -518,13 +518,12 @@ useEffect(() => {
Check platform availability when using platform-specific events:
```javascript
-import { Platform, Events } from '@wailsio/runtime';
+import { Events } from '@wailsio/runtime';
-if (Platform.isWindows) {
- Events.On('windows:APMSuspend', handleSuspend);
-} else if (Platform.isMac) {
- Events.On('mac:ApplicationWillTerminate', handleTerminate);
-}
+// Platform-specific events can be registered unconditionally;
+// they will simply never fire on unsupported platforms.
+Events.On('windows:APMSuspend', handleSuspend);
+Events.On('mac:ApplicationWillTerminate', handleTerminate);
```
### 4. Don't Overuse Events
diff --git a/docs/src/content/docs/migration/v2-to-v3.mdx b/docs/src/content/docs/migration/v2-to-v3.mdx
index 5eb94ca67..002b98663 100644
--- a/docs/src/content/docs/migration/v2-to-v3.mdx
+++ b/docs/src/content/docs/migration/v2-to-v3.mdx
@@ -456,13 +456,13 @@ EventsOn("update", (data) => {
EventsEmit("action", data)
// v3
-import { OnEvent, Emit } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-OnEvent("update", (data) => {
+Events.On("update", (data) => {
console.log(data)
})
-Emit("action", data)
+Events.Emit("action", data)
```
### Step 6: Update Configuration
diff --git a/docs/src/content/docs/reference/events.mdx b/docs/src/content/docs/reference/events.mdx
index d6286018e..15e9ae0a4 100644
--- a/docs/src/content/docs/reference/events.mdx
+++ b/docs/src/content/docs/reference/events.mdx
@@ -97,9 +97,9 @@ app.Event.Emit("global-update", data)
Listens for events from Go.
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-On(eventName, callback)
+Events.On(eventName, callback)
```
**Parameters:**
@@ -110,10 +110,10 @@ On(eventName, callback)
**Example:**
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Listen for events
-const cleanup = On('data-updated', (data) => {
+const cleanup = Events.On('data-updated', (data) => {
console.log('Count:', data.count)
console.log('Status:', data.status)
updateUI(data)
@@ -128,17 +128,17 @@ cleanup()
Listens for a single event occurrence.
```javascript
-import { Once } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-Once(eventName, callback)
+Events.Once(eventName, callback)
```
**Example:**
```javascript
-import { Once } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Listen for first occurrence only
-Once('initialization-complete', (data) => {
+Events.Once('initialization-complete', (data) => {
console.log('App initialized!', data)
// This will only fire once
})
@@ -149,24 +149,24 @@ Once('initialization-complete', (data) => {
Removes an event listener.
```javascript
-import { Off } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-Off(eventName, callback)
+Events.Off(eventName, callback)
```
**Example:**
```javascript
-import { On, Off } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
const handler = (data) => {
console.log('Event received:', data)
}
// Start listening
-On('my-event', handler)
+Events.On('my-event', handler)
// Stop listening
-Off('my-event', handler)
+Events.Off('my-event', handler)
```
### OffAll()
@@ -174,9 +174,9 @@ Off('my-event', handler)
Removes all listeners for an event.
```javascript
-import { OffAll } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-OffAll(eventName)
+Events.OffAll(eventName)
```
**Example:**
@@ -281,10 +281,10 @@ func (s *DataService) FetchData(query string) ([]Item, error) {
```javascript
import { FetchData } from './bindings/DataService'
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Listen for completion event
-On('data-fetched', (data) => {
+Events.On('data-fetched', (data) => {
console.log(`Fetched ${data.count} items for query: ${data.query}`)
showNotification(`Found ${data.count} results`)
})
@@ -325,16 +325,16 @@ func (s *Service) ProcessFiles(files []string) error {
**JavaScript:**
```javascript
-import { On, Once } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Update progress bar
-On('progress', (data) => {
+Events.On('progress', (data) => {
progressBar.style.width = `${data.percent}%`
statusText.textContent = `Processing ${data.file}... (${data.current}/${data.total})`
})
// Handle completion
-Once('processing-complete', () => {
+Events.Once('processing-complete', () => {
progressBar.style.width = '100%'
statusText.textContent = 'Complete!'
setTimeout(() => hideProgressBar(), 2000)
@@ -364,10 +364,10 @@ window1.OnEvent("request-data", func(e *application.CustomEvent) {
**JavaScript:**
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Listen in any window
-On('theme-changed', (theme) => {
+Events.On('theme-changed', (theme) => {
document.body.className = theme
})
```
@@ -407,13 +407,13 @@ func (s *StateService) GetState(key string) interface{} {
**JavaScript:**
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
import { GetState } from './bindings/StateService'
// Keep local state in sync
let localState = {}
-On('state-updated', async (data) => {
+Events.On('state-updated', async (data) => {
localState[data.key] = data.value
updateUI(data.key, data.value)
})
@@ -459,10 +459,10 @@ func (s *NotificationService) Info(message string) {
**JavaScript:**
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Unified notification handler
-On('notification', (data) => {
+Events.On('notification', (data) => {
const toast = document.createElement('div')
toast.className = `toast toast-${data.type}`
toast.textContent = data.message
@@ -568,34 +568,34 @@ func main() {
**JavaScript:**
```javascript
-import { On, Once } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
import { StartLongTask, BroadcastMessage } from './bindings/EventDemoService'
// Task events
-On('task-started', () => {
+Events.On('task-started', () => {
console.log('Task started...')
document.getElementById('status').textContent = 'Running...'
})
-On('task-progress', (data) => {
+Events.On('task-progress', (data) => {
const progressBar = document.getElementById('progress')
progressBar.style.width = `${data.percent}%`
console.log(`Step ${data.step} of ${data.total}`)
})
-Once('task-completed', (data) => {
+Events.Once('task-completed', (data) => {
console.log('Task completed!', data.message)
document.getElementById('status').textContent = data.message
})
// Broadcast events
-On('broadcast', (message) => {
+Events.On('broadcast', (message) => {
console.log('Broadcast:', message)
alert(message)
})
// Window state events
-On('window-state', (state) => {
+Events.On('window-state', (state) => {
console.log('Window is now:', state)
document.body.dataset.windowState = state
})
@@ -784,12 +784,12 @@ func (s *Service) EmitWithDebounce(event string, data interface{}) {
### Throttling Events
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
let lastUpdate = 0
const throttleMs = 100
-On('high-frequency-event', (data) => {
+Events.On('high-frequency-event', (data) => {
const now = Date.now()
if (now - lastUpdate < throttleMs) {
return // Skip this update
diff --git a/docs/src/content/docs/reference/frontend-runtime.mdx b/docs/src/content/docs/reference/frontend-runtime.mdx
index 4d56667fc..1f02ececa 100644
--- a/docs/src/content/docs/reference/frontend-runtime.mdx
+++ b/docs/src/content/docs/reference/frontend-runtime.mdx
@@ -159,9 +159,7 @@ Events.Emit(UserUpdated({
The runtime is organized into modules, each providing specific functionality. Import only what you need:
```javascript
-import { On, Emit } from '@wailsio/runtime/events'
-import Window from '@wailsio/runtime/window'
-import { SetText, Text } from '@wailsio/runtime/clipboard'
+import { Events, Window, Clipboard } from '@wailsio/runtime'
```
### Events
@@ -182,17 +180,17 @@ function On(eventType: EventType, callback: (event: WailsEvent) => void
**Example:**
```javascript
-import { On } from '@wailsio/runtime/events'
+import { Events } from '@wailsio/runtime'
// Basic event listening
-const unsubscribe = On('user-logged-in', (event) => {
+const unsubscribe = Events.On('user-logged-in', (event) => {
console.log('User:', event.data.username)
})
// With typed events (TypeScript)
import { UserLogin } from './bindings/events'
-On(UserLogin, (event) => {
+Events.On(UserLogin, (event) => {
// event.data is typed as UserLoginData
console.log('User:', event.data.username)
})
@@ -212,9 +210,9 @@ function Once(eventType: EventType, callback: (event: WailsEvent) => vo
**Example:**
```javascript
-import { Once } from '@wailsio/runtime/events'
+import { Events } from '@wailsio/runtime'
-Once('app-ready', () => {
+Events.Once('app-ready', () => {
console.log('App initialized')
})
```
@@ -233,15 +231,15 @@ function Emit(event: Event): Promise
**Example:**
```javascript
-import { Emit } from '@wailsio/runtime/events'
+import { Events } from '@wailsio/runtime'
// Basic event emission
-const wasCancelled = await Emit('button-clicked', { buttonId: 'submit' })
+const wasCancelled = await Events.Emit('button-clicked', { buttonId: 'submit' })
// With typed events (TypeScript)
import { UserLogin } from './bindings/events'
-const cancelled = await Emit(UserLogin({
+const cancelled = await Events.Emit(UserLogin({
UserID: "123",
Username: "john_doe",
LoginTime: new Date().toISOString()
@@ -267,9 +265,9 @@ function Off(...eventNames: string[]): void
**Example:**
```javascript
-import { Off } from '@wailsio/runtime/events'
+import { Events } from '@wailsio/runtime'
-Off('user-logged-in', 'user-logged-out')
+Events.Off('user-logged-in', 'user-logged-out')
```
#### OffAll()
@@ -285,7 +283,7 @@ function OffAll(): void
Window management methods. The default export is the current window.
```javascript
-import Window from '@wailsio/runtime/window'
+import { Window } from '@wailsio/runtime'
// Current window
await Window.SetTitle('New Title')
@@ -351,7 +349,7 @@ function Center(): Promise
**Example:**
```javascript
-import Window from '@wailsio/runtime/window'
+import { Window } from '@wailsio/runtime'
// Resize and center
await Window.SetSize(800, 600)
@@ -514,7 +512,7 @@ function Print(): Promise
**Example:**
```javascript
-import Window from '@wailsio/runtime/window'
+import { Window } from '@wailsio/runtime'
// Open print dialog for current window
await Window.Print()
@@ -537,9 +535,9 @@ function SetText(text: string): Promise
**Example:**
```javascript
-import { SetText } from '@wailsio/runtime/clipboard'
+import { Clipboard } from '@wailsio/runtime'
-await SetText('Hello from Wails!')
+await Clipboard.SetText('Hello from Wails!')
```
#### Text()
@@ -553,9 +551,9 @@ function Text(): Promise
**Example:**
```javascript
-import { Text } from '@wailsio/runtime/clipboard'
+import { Clipboard } from '@wailsio/runtime'
-const clipboardText = await Text()
+const clipboardText = await Clipboard.Text()
console.log('Clipboard:', clipboardText)
```
@@ -620,11 +618,11 @@ function Quit(): Promise
**Example:**
```javascript
-import { Quit } from '@wailsio/runtime/application'
+import { Application } from '@wailsio/runtime'
// Add quit button
document.getElementById('quit-btn').addEventListener('click', async () => {
- await Quit()
+ await Application.Quit()
})
```
@@ -643,9 +641,9 @@ function OpenURL(url: string | URL): Promise
**Example:**
```javascript
-import { OpenURL } from '@wailsio/runtime/browser'
+import { Browser } from '@wailsio/runtime'
-await OpenURL('https://wails.io')
+await Browser.OpenURL('https://wails.io')
```
### Screens
@@ -696,16 +694,16 @@ interface Screen {
**Example:**
```javascript
-import { GetAll, GetPrimary } from '@wailsio/runtime/screens'
+import { Screens } from '@wailsio/runtime'
// List all screens
-const screens = await GetAll()
+const screens = await Screens.GetAll()
screens.forEach(screen => {
console.log(`${screen.Name}: ${screen.Size.Width}x${screen.Size.Height}`)
})
// Get primary screen
-const primary = await GetPrimary()
+const primary = await Screens.GetPrimary()
console.log('Primary screen:', primary.Name)
```
@@ -724,9 +722,9 @@ function Info(options: MessageDialogOptions): Promise
**Example:**
```javascript
-import { Info } from '@wailsio/runtime/dialogs'
+import { Dialogs } from '@wailsio/runtime'
-await Info({
+await Dialogs.Info({
Title: 'Success',
Message: 'Operation completed successfully!'
})
@@ -759,9 +757,9 @@ function Question(options: QuestionDialogOptions): Promise
**Example:**
```javascript
-import { Question } from '@wailsio/runtime/dialogs'
+import { Dialogs } from '@wailsio/runtime'
-const result = await Question({
+const result = await Dialogs.Question({
Title: 'Confirm Delete',
Message: 'Are you sure you want to delete this file?',
Buttons: [
@@ -786,9 +784,9 @@ function OpenFile(options: OpenFileDialogOptions): Promise
**Example:**
```javascript
-import { OpenFile } from '@wailsio/runtime/dialogs'
+import { Dialogs } from '@wailsio/runtime'
-const file = await OpenFile({
+const file = await Dialogs.OpenFile({
Title: 'Select Image',
Filters: [
{ DisplayName: 'Images', Pattern: '*.png;*.jpg;*.jpeg' },
@@ -864,14 +862,10 @@ WML provides declarative attributes for common actions. Add attributes to HTML e
## Complete Example
```javascript
-import { On, Emit } from '@wailsio/runtime/events'
-import Window from '@wailsio/runtime/window'
-import { SetText, Text } from '@wailsio/runtime/clipboard'
-import { Question } from '@wailsio/runtime/dialogs'
-import { GetAll } from '@wailsio/runtime/screens'
+import { Events, Window, Clipboard, Dialogs, Screens } from '@wailsio/runtime'
// Listen for events from Go
-On('data-updated', (event) => {
+Events.On('data-updated', (event) => {
console.log('Data:', event.data)
updateUI(event.data)
})
@@ -892,12 +886,12 @@ document.getElementById('fullscreen-btn').addEventListener('click', async () =>
// Clipboard operations
document.getElementById('copy-btn').addEventListener('click', async () => {
- await SetText('Copied from Wails!')
+ await Clipboard.SetText('Copied from Wails!')
})
// Dialog with confirmation
document.getElementById('delete-btn').addEventListener('click', async () => {
- const result = await Question({
+ const result = await Dialogs.Question({
Title: 'Confirm',
Message: 'Delete this item?',
Buttons: [
@@ -907,12 +901,12 @@ document.getElementById('delete-btn').addEventListener('click', async () => {
})
if (result === 'Delete') {
- await Emit('delete-item', { id: currentItemId })
+ await Events.Emit('delete-item', { id: currentItemId })
}
})
// Screen information
-const screens = await GetAll()
+const screens = await Screens.GetAll()
console.log(`Detected ${screens.length} screen(s)`)
screens.forEach(screen => {
console.log(`- ${screen.Name}: ${screen.Size.Width}x${screen.Size.Height}`)
@@ -940,10 +934,9 @@ screens.forEach(screen => {
The runtime includes full TypeScript definitions:
```typescript
-import { On, WailsEvent } from '@wailsio/runtime/events'
-import Window from '@wailsio/runtime/window'
+import { Events, Window } from '@wailsio/runtime'
-On('custom-event', (event: WailsEvent) => {
+Events.On('custom-event', (event) => {
// TypeScript knows event.data, event.name, event.sender
console.log(event.data)
})
diff --git a/docs/src/content/docs/reference/overview.mdx b/docs/src/content/docs/reference/overview.mdx
index 8a3b77bc3..064edbf4a 100644
--- a/docs/src/content/docs/reference/overview.mdx
+++ b/docs/src/content/docs/reference/overview.mdx
@@ -146,8 +146,7 @@ import (
import { MyMethod } from './bindings/MyService'
// Runtime APIs
-import { On, Emit } from '@wailsio/runtime'
-import { WindowSetTitle } from '@wailsio/runtime/window'
+import { Events, Window } from '@wailsio/runtime'
```
## Type Reference
diff --git a/docs/src/content/docs/reference/window.mdx b/docs/src/content/docs/reference/window.mdx
index 346a326e6..9d6dd5102 100644
--- a/docs/src/content/docs/reference/window.mdx
+++ b/docs/src/content/docs/reference/window.mdx
@@ -498,9 +498,9 @@ window.EmitEvent("data-updated", map[string]interface{}{
**Frontend (JavaScript):**
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-On('data-updated', (data) => {
+Events.On('data-updated', (data) => {
console.log('Count:', data.count)
console.log('Status:', data.status)
})
@@ -597,12 +597,27 @@ if err != nil {
}
```
-**Platform support:**
-- **macOS**: Full support
-- **Windows**: Full support
-- **Linux**: Full support
+### AttachModal()
-**Note:** This triggers the native OS print dialog, allowing the user to select printer settings and print the current window content.
+Attaches a second Window as a sheet modal.
+
+```go
+func (w *Window) AttachModal(modalWindow Window)
+```
+
+**Parameters:**
+- `modalWindow` - The window to attach as a modal
+
+**Platform support:**
+- **macOS**: Full support (presents as a sheet)
+- **Windows**: No support
+- **Linux**: No support
+
+**Example:**
+```go
+modalWindow := app.Window.New()
+window.AttachModal(modalWindow)
+```
## Platform-Specific Options
diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md
index e9af45432..35c356b07 100644
--- a/v3/UNRELEASED_CHANGELOG.md
+++ b/v3/UNRELEASED_CHANGELOG.md
@@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file
## Added
+- Add support for modal sheets (macOS)
## Changed
@@ -25,6 +26,7 @@ After processing, the content will be moved to the main changelog and this file
- Fix system tray menu highlight state on macOS - icon now shows selected state when menu is open (#4910)
- Fix system tray attached window appearing behind other windows on macOS - now uses proper popup window level (#4910)
+- Fix incorrect `@wailsio/runtime` import examples across documentation (#4989)
## Deprecated
diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt
index 080cb6498..c4b06870c 100644
--- a/v3/internal/version/version.txt
+++ b/v3/internal/version/version.txt
@@ -1 +1 @@
-v3.0.0-alpha.72
\ No newline at end of file
+v3.0.0-alpha.73
\ No newline at end of file
diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go
index 6cfa8b821..91909e83e 100644
--- a/v3/pkg/application/webview_window.go
+++ b/v3/pkg/application/webview_window.go
@@ -113,6 +113,7 @@ type (
setMenu(menu *Menu)
snapAssist()
setContentProtection(enabled bool)
+ attachModal(modalWindow *WebviewWindow)
}
)
@@ -1284,6 +1285,22 @@ func (w *WebviewWindow) NativeWindow() unsafe.Pointer {
return w.impl.nativeWindow()
}
+// AttachModal attaches a modal window to this window, presenting it as a sheet on macOS.
+func (w *WebviewWindow) AttachModal(modalWindow Window) {
+ if w.impl == nil || w.isDestroyed() {
+ return
+ }
+
+ modalWebviewWindow, ok := modalWindow.(*WebviewWindow)
+ if !ok || modalWebviewWindow == nil {
+ return
+ }
+
+ InvokeSync(func() {
+ w.impl.attachModal(modalWebviewWindow)
+ })
+}
+
// shouldUnconditionallyClose returns whether the window should close unconditionally
func (w *WebviewWindow) shouldUnconditionallyClose() bool {
return atomic.LoadUint32(&w.unconditionallyClose) != 0
diff --git a/v3/pkg/application/webview_window_android.go b/v3/pkg/application/webview_window_android.go
index ed41a5add..6e139f52c 100644
--- a/v3/pkg/application/webview_window_android.go
+++ b/v3/pkg/application/webview_window_android.go
@@ -296,6 +296,10 @@ func (w *androidWebviewWindow) nativeWindow() unsafe.Pointer {
return nil
}
+func (w *androidWebviewWindow) attachModal(modalWindow *WebviewWindow) {
+ // Modal windows are not supported on Android
+}
+
func (w *androidWebviewWindow) on(eventID uint) {
// Android event handling
}
diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go
index ff49ad49b..14fef21da 100644
--- a/v3/pkg/application/webview_window_darwin.go
+++ b/v3/pkg/application/webview_window_darwin.go
@@ -27,7 +27,7 @@ extern void registerListener(unsigned int event);
void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences) {
NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
if (frameless) {
- styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable;
+ styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable;
}
WebviewWindow* window = [[WebviewWindow alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1)
styleMask:styleMask
@@ -331,6 +331,24 @@ void windowZoomOut(void* nsWindow) {
}
}
+// createModalWindow presents a modal window as a sheet attached to the parent window
+void createModalWindow(void* parentWindowPtr, void* modalWindowPtr) {
+ if (parentWindowPtr == NULL || modalWindowPtr == NULL) {
+ return;
+ }
+
+ NSWindow* parentWindow = (NSWindow*)parentWindowPtr;
+ NSWindow* modalWindow = (NSWindow*)modalWindowPtr;
+
+ // Present the modal window as a sheet attached to the parent window
+ // Must be dispatched to the main thread for UI thread safety
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [parentWindow beginSheet:modalWindow completionHandler:^(NSModalResponse returnCode) {
+ // Sheet was dismissed - window will be released automatically
+ }];
+ });
+}
+
// set the window position relative to the screen
void windowSetRelativePosition(void* nsWindow, int x, int y) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
@@ -1551,6 +1569,17 @@ func (w *macosWebviewWindow) setContentProtection(enabled bool) {
C.setContentProtection(w.nsWindow, C.bool(enabled))
}
+func (w *macosWebviewWindow) attachModal(modalWindow *WebviewWindow) {
+ if modalWindow == nil || modalWindow.impl == nil || modalWindow.isDestroyed() {
+ return
+ }
+ modalNativeWindow := modalWindow.impl.nativeWindow()
+ if modalNativeWindow == nil {
+ return
+ }
+ C.createModalWindow(w.nsWindow, modalNativeWindow)
+}
+
func (w *macosWebviewWindow) cut() {
}
diff --git a/v3/pkg/application/webview_window_ios.go b/v3/pkg/application/webview_window_ios.go
index bc3087f2c..481788c87 100644
--- a/v3/pkg/application/webview_window_ios.go
+++ b/v3/pkg/application/webview_window_ios.go
@@ -330,6 +330,10 @@ func (w *iosWebviewWindow) nativeWindow() unsafe.Pointer {
return w.nativeHandle
}
+func (w *iosWebviewWindow) attachModal(modalWindow *WebviewWindow) {
+ // Modal windows are not supported on iOS
+}
+
func (w *iosWebviewWindow) on(eventID uint) {
// iOS event handling
}
diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go
index 9a5eb4131..4eddadbed 100644
--- a/v3/pkg/application/webview_window_linux.go
+++ b/v3/pkg/application/webview_window_linux.go
@@ -410,6 +410,10 @@ func (w *linuxWebviewWindow) nativeWindow() unsafe.Pointer {
return unsafe.Pointer(w.window)
}
+func (w *linuxWebviewWindow) attachModal(modalWindow *WebviewWindow) {
+ // Modal windows are not supported on Linux
+}
+
func (w *linuxWebviewWindow) print() error {
w.execJS("window.print();")
return nil
diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go
index 16ba08f0b..434723fb4 100644
--- a/v3/pkg/application/webview_window_windows.go
+++ b/v3/pkg/application/webview_window_windows.go
@@ -78,6 +78,9 @@ type windowsWebviewWindow struct {
// menubarTheme is the theme for the menubar
menubarTheme *w32.MenuBarTheme
+
+ // Modal window tracking
+ parentHWND w32.HWND // Parent window HWND when this window is a modal
}
func (w *windowsWebviewWindow) setMenu(menu *Menu) {
@@ -233,6 +236,37 @@ func (w *windowsWebviewWindow) startDrag() error {
return nil
}
+func (w *windowsWebviewWindow) attachModal(modalWindow *WebviewWindow) {
+ if modalWindow == nil || modalWindow.impl == nil || modalWindow.isDestroyed() {
+ return
+ }
+
+ // Get the modal window's Windows implementation
+ modalWindowsImpl, ok := modalWindow.impl.(*windowsWebviewWindow)
+ if !ok {
+ return
+ }
+
+ parentHWND := w.hwnd
+ modalHWND := modalWindowsImpl.hwnd
+
+ // Set parent-child relationship using GWLP_HWNDPARENT
+ // This ensures the modal stays above parent and moves with it
+ w32.SetWindowLongPtr(modalHWND, w32.GWLP_HWNDPARENT, uintptr(parentHWND))
+
+ // Track the parent HWND in the modal window for cleanup
+ modalWindowsImpl.parentHWND = parentHWND
+
+ // Disable the parent window to block interaction (Microsoft's recommended approach)
+ // This follows Windows modal dialog best practices
+ w32.EnableWindow(parentHWND, false)
+
+ // Ensure modal window is shown and brought to front
+ w32.ShowWindow(modalHWND, w32.SW_SHOW)
+ w32.SetForegroundWindow(modalHWND)
+ w32.BringWindowToTop(modalHWND)
+}
+
func (w *windowsWebviewWindow) nativeWindow() unsafe.Pointer {
return unsafe.Pointer(w.hwnd)
}
@@ -725,6 +759,12 @@ func (w *windowsWebviewWindow) setRelativePosition(x int, y int) {
}
func (w *windowsWebviewWindow) destroy() {
+ // Re-enable parent window if this was a modal window
+ if w.parentHWND != 0 {
+ w32.EnableWindow(w.parentHWND, true)
+ w.parentHWND = 0
+ }
+
w.parent.markAsDestroyed()
// destroy the window
w32.DestroyWindow(w.hwnd)
@@ -1420,6 +1460,12 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp
}
defer func() {
+ // Re-enable parent window if this was a modal window
+ if w.parentHWND != 0 {
+ w32.EnableWindow(w.parentHWND, true)
+ w.parentHWND = 0
+ }
+
windowsApp := globalApplication.impl.(*windowsApp)
windowsApp.unregisterWindow(w)
diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go
index 3f4949b16..ec810b115 100644
--- a/v3/pkg/application/window.go
+++ b/v3/pkg/application/window.go
@@ -91,6 +91,7 @@ type Window interface {
Flash(enabled bool)
Print() error
RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func()
+ AttachModal(modalWindow Window)
shouldUnconditionallyClose() bool
// Editing methods