From cdb3110857ade784f53a0f85e7c3198cb78c4aa8 Mon Sep 17 00:00:00 2001 From: sabrimete <49492573+sabrimete@users.noreply.github.com> Date: Tue, 3 Nov 2026 07:07:05 +0000 Subject: [PATCH] fix(ui): honor the default font in showFontPicker on the desktop (#4340) * fix(ui): honor the default font in showFontPicker on the desktop String arguments are normalized to { defaultFont } in the IPC handler, matching showColorPicker, instead of throwing on property assignment. UIWindowFontPicker reads defaultValue/defaultFont/default case- insensitively, and the SDK fallback and docs accept the same aliases. * fix(ui): document the showFontPicker cancel result and alias order The JSDoc gains overloads for the string and options call forms and states that closing the picker resolves false in an app and null on a website; the docs page and examples match. e2e coverage adds a scroll-into-view check for the preselected font and a close-without- selecting case. --- src/docs/src/UI/showFontPicker.md | 11 ++- .../examples/ui-show-font-picker.html | 6 +- src/gui/src/IPC.js | 3 + src/gui/src/UI/UIWindowFontPicker.js | 14 ++-- src/puter-js/src/modules/UI.js | 24 ++++-- .../tests/e2e/fixtures/font-picker.html | 70 ++++++++++++++++++ .../tests/e2e/specs/showFontPicker.spec.js | 73 +++++++++++++++++++ 7 files changed, 187 insertions(+), 14 deletions(-) create mode 100644 src/puter-js/tests/e2e/fixtures/font-picker.html create mode 100644 src/puter-js/tests/e2e/specs/showFontPicker.spec.js diff --git a/src/docs/src/UI/showFontPicker.md b/src/docs/src/UI/showFontPicker.md index 437742b22..6453795f2 100755 --- a/src/docs/src/UI/showFontPicker.md +++ b/src/docs/src/UI/showFontPicker.md @@ -17,6 +17,13 @@ puter.ui.showFontPicker(options) #### `defaultFont` (String) The default font to select when the font picker is opened. +#### `options` (Object) +An object with the following properties: + +- `defaultFont` (String): The font to select when the font picker is opened. The name is matched without regard to case. `defaultValue` and `default` are accepted as aliases. If more than one is given, `defaultValue` is used, then `defaultFont`, then `default`. + +## Return value +A `Promise` that resolves to an object with a `fontFamily` property holding the name of the selected font. If the user closes the picker without selecting a font, the promise resolves to `false` in a Puter app and to `null` on a website. ## Examples @@ -28,7 +35,9 @@ The default font to select when the font picker is opened. diff --git a/src/docs/src/playground/examples/ui-show-font-picker.html b/src/docs/src/playground/examples/ui-show-font-picker.html index 8411565f4..7570e090a 100644 --- a/src/docs/src/playground/examples/ui-show-font-picker.html +++ b/src/docs/src/playground/examples/ui-show-font-picker.html @@ -6,8 +6,10 @@ diff --git a/src/gui/src/IPC.js b/src/gui/src/IPC.js index 0f238a69e..ca462f674 100644 --- a/src/gui/src/IPC.js +++ b/src/gui/src/IPC.js @@ -1630,6 +1630,9 @@ const ipc_listener = async (event, handled) => { } // set options + if ( typeof event.data.options === 'string' ) { + event.data.options = { defaultFont: event.data.options }; + } event.data.options = event.data.options ?? {}; // clear window_options for security reasons diff --git a/src/gui/src/UI/UIWindowFontPicker.js b/src/gui/src/UI/UIWindowFontPicker.js index 1e1bd5400..013207580 100644 --- a/src/gui/src/UI/UIWindowFontPicker.js +++ b/src/gui/src/UI/UIWindowFontPicker.js @@ -41,7 +41,7 @@ const font_list = new Set([ async function UIWindowFontPicker (options) { // set sensible defaults if ( arguments.length > 0 ) { - // if first argument is a string, then assume it is the default color + // if first argument is a string, then assume it is the default font if ( window.isString(arguments[0]) ) { options = {}; options.default = arguments[0]; @@ -49,13 +49,17 @@ async function UIWindowFontPicker (options) { } options = options || {}; + const defaultFont = options.defaultValue ?? options.defaultFont ?? options.default; + const isDefault = (font) => window.isString(defaultFont) + && font.toLowerCase() === defaultFont.trim().toLowerCase(); + return new Promise(async (resolve) => { let h = ''; h += '
'; h += '
'; h += '
'; fontAvailable.forEach(element => { - h += `

${html_encode(element)}

`; // 👉️ one, two, three, four + h += `

${html_encode(element)}

`; // 👉️ one, two, three, four }); h += '
'; @@ -89,10 +93,10 @@ async function UIWindowFontPicker (options) { on_close: () => { resolve(false); }, - onAppend: function (window) { - let active_font = $(window).find('.font-selector-active'); + onAppend: function (win) { + let active_font = $(win).find('.font-selector-active'); if ( active_font.length > 0 ) { - window.scrollParentToChild($(window).find('.font-list').get(0), active_font.get(0)); + window.scrollParentToChild($(win).find('.font-list').get(0), active_font.get(0)); } }, window_class: 'window-login', diff --git a/src/puter-js/src/modules/UI.js b/src/puter-js/src/modules/UI.js index 4e29ace25..3e1b2a229 100644 --- a/src/puter-js/src/modules/UI.js +++ b/src/puter-js/src/modules/UI.js @@ -1419,12 +1419,24 @@ export class UIModule extends EventListener { }; /** - * Shows a font picker. Resolves to the chosen font. Accepts either a - * default font name or an options object. `default` is a legacy alias for - * `defaultFont`. + * @overload + * @param {string} [defaultFont] + * @returns {Promise<{ fontFamily: string } | false | null>} + */ + /** + * @overload + * @param {FontPickerOptions & { default?: string, defaultValue?: string }} [options] + * @returns {Promise<{ fontFamily: string } | false | null>} + */ + /** + * Shows a font picker. Resolves to the chosen font, or to `false` inside a + * Puter app and `null` on a website when the user closes the picker without + * choosing. Accepts either a default font name or an options object. + * `default` and `defaultValue` are legacy aliases for `defaultFont`; + * `defaultValue` wins over `defaultFont`, which wins over `default`. * - * @param {string | (FontPickerOptions & { default?: string })} [options] - * @returns {Promise<{ fontFamily: string }>} + * @param {string | (FontPickerOptions & { default?: string, defaultValue?: string })} [options] + * @returns {Promise<{ fontFamily: string } | false | null>} */ showFontPicker (options) { if ( this.messageTarget ) { @@ -1436,7 +1448,7 @@ export class UIModule extends EventListener { return new Promise((resolve) => { const opts = typeof options === 'string' ? { defaultFont: options } : (options ?? {}); const el = document.createElement('puter-font-picker'); - const defaultFont = opts.defaultFont || opts.default || 'System UI'; + const defaultFont = opts.defaultValue || opts.defaultFont || opts.default || 'System UI'; el.setAttribute('default-font', defaultFont); el.addEventListener('response', (e) => resolve(e.detail)); document.body.appendChild(el); diff --git a/src/puter-js/tests/e2e/fixtures/font-picker.html b/src/puter-js/tests/e2e/fixtures/font-picker.html new file mode 100644 index 000000000..a0f2d5e3a --- /dev/null +++ b/src/puter-js/tests/e2e/fixtures/font-picker.html @@ -0,0 +1,70 @@ + + + + + puter-js test fixture: showFontPicker + + + +

puter-js showFontPicker fixture

+

Loading puter.js…

+ + + + + + +
+ + + + + + diff --git a/src/puter-js/tests/e2e/specs/showFontPicker.spec.js b/src/puter-js/tests/e2e/specs/showFontPicker.spec.js new file mode 100644 index 000000000..94b9da8a3 --- /dev/null +++ b/src/puter-js/tests/e2e/specs/showFontPicker.spec.js @@ -0,0 +1,73 @@ +import { test, expect } from '@playwright/test'; +import { registerTestApp, deleteTestApp, gotoTestApp, FIXTURE_URL } from '../helpers/testApp.js'; + +const FONT_FIXTURE_URL = FIXTURE_URL.replace( + 'menubar-contextmenu.html', + 'font-picker.html', +); + +test.describe('puter.ui.showFontPicker (env=app)', () => { + for ( const [buttonId, label] of [ + ['font-string', 'a string argument'], + ['font-default-font', '{ defaultFont }'], + ['font-default-value', '{ defaultValue }'], + ] ) { + test(`preselects the default font with ${label}`, async ({ page }) => { + const appName = await registerTestApp(page, { fixtureURL: FONT_FIXTURE_URL }); + try { + const appFrame = await gotoTestApp(page, appName); + + await appFrame.locator(`#${buttonId}`).click(); + const dialog = page.locator('.window[data-app="font-picker"]'); + await expect(dialog).toBeVisible({ timeout: 15_000 }); + + // Exactly one preselected entry, and it is the default we sent + // (defaultValue is passed lowercase on purpose: the match is + // case-insensitive). + const active = dialog.locator('.font-selector-active'); + await expect(active).toHaveCount(1); + await expect(active).toHaveAttribute('data-font-family', 'Georgia'); + + await dialog.locator('.select-btn').click(); + await expect(appFrame.locator('#log [data-entry="font:Georgia"]')).toBeVisible(); + } finally { + await deleteTestApp(page, appName); + } + }); + } + + test('scrolls the preselected font into view', async ({ page }) => { + const appName = await registerTestApp(page, { fixtureURL: FONT_FIXTURE_URL }); + try { + const appFrame = await gotoTestApp(page, appName); + + await appFrame.locator('#font-verdana').click(); + const dialog = page.locator('.window[data-app="font-picker"]'); + await expect(dialog).toBeVisible({ timeout: 15_000 }); + + const active = dialog.locator('.font-selector-active'); + await expect(active).toHaveCount(1); + await expect(active).toHaveAttribute('data-font-family', 'Verdana'); + await expect(active).toBeInViewport(); + } finally { + await deleteTestApp(page, appName); + } + }); + + test('resolves false when the picker is closed', async ({ page }) => { + const appName = await registerTestApp(page, { fixtureURL: FONT_FIXTURE_URL }); + try { + const appFrame = await gotoTestApp(page, appName); + + await appFrame.locator('#font-string').click(); + const dialog = page.locator('.window[data-app="font-picker"]'); + await expect(dialog).toBeVisible({ timeout: 15_000 }); + + await dialog.locator('.window-close-btn').click(); + await expect(dialog).toBeHidden(); + await expect(appFrame.locator('#log [data-entry="font:cancel"]')).toBeVisible(); + } finally { + await deleteTestApp(page, appName); + } + }); +});