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); + } + }); +});