@freedam/picker 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -0
- package/README.md +122 -2
- package/dist/chunk-SF2R2FAB.js +2 -0
- package/dist/chunk-SF2R2FAB.js.map +1 -0
- package/dist/element-D6ex-aQ4.d.ts +401 -0
- package/dist/element.d.ts +1 -0
- package/dist/element.js +2 -0
- package/dist/element.js.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +471 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/picker.global.js +1 -0
- package/package.json +69 -5
- package/schema/payload.schema.json +316 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/protocol.ts","../src/options.ts","../src/frame.ts","../src/popup.ts","../src/open.ts","../src/version.ts"],"sourcesContent":["export { open } from './open';\n// Types only: importing '@freedam/picker/element' defines the element.\nexport type { FreedamPickerElement, FreedamPickerEventMap } from './element';\nexport type { PickerOptions } from './options';\nexport { VERSION } from './version';\nexport { ERROR_MESSAGES, FreedamPickerError, SDK_ERROR_CODES, SERVER_ERROR_CODES } from './errors';\nexport type { PickerErrorCode, PickerSdkErrorCode, PickerServerErrorCode } from './errors';\nexport type {\n PickerAsset,\n PickerAssetType,\n PickerDocument,\n PickerDocumentChoice,\n PickerDocumentFile,\n PickerDocumentForm,\n PickerFileFormat,\n PickerFocusPoint,\n PickerFormat,\n PickerImage,\n PickerImageChoice,\n PickerMetadata,\n PickerMetadataValue,\n PickerSubtitle,\n PickerVideo,\n PickerVideoChoice,\n} from './payload';\nexport { INLINE_MIN_HEIGHT, MAX_SELECTION, PICKER_ASSET_TYPES, PICKER_LOCALES, PICKER_MODES, PICKER_THEMES, PROTOCOL_VERSION, SORT_DIRECTIONS, SORT_FIELDS } from './protocol';\nexport type {\n HostCloseMessage,\n HostToPickerMessage,\n PickerCancelMessage,\n PickerErrorMessage,\n PickerLocale,\n PickerMessageBase,\n PickerFrameMode,\n PickerMode,\n PickerOpenQuery,\n PickerReadyMessage,\n PickerResizeMessage,\n PickerSelectMessage,\n PickerSignInMessage,\n PickerSelectMode,\n PickerTheme,\n PickerToHostMessage,\n RuleFilter,\n RuleGroup,\n RuleTree,\n SortDirection,\n SortField,\n} from './protocol';\n","/**\n * Codes the picker page sends in an `error` message.\n *\n * Keep in sync with `App\\Enums\\PickerErrorCode`: `tests/Unit/Enums/PickerErrorCodeTest.php`\n * reads this list and fails when the two differ. `auth_required` belongs to\n * the iframe modes and `guest_access_unavailable` to the access without an\n * account (version 1.1); the picker never sends `auth_required` in a popup.\n */\nexport const SERVER_ERROR_CODES = [\n 'origin_denied',\n 'feature_unavailable',\n 'integration_suspended',\n 'integration_unavailable',\n 'insert_failed',\n 'auth_required',\n 'guest_access_unavailable',\n] as const;\n\n/** Codes the SDK raises itself, without the picker page. */\nexport const SDK_ERROR_CODES = ['popup_blocked', 'invalid_options'] as const;\n\nexport type PickerServerErrorCode = (typeof SERVER_ERROR_CODES)[number];\n\nexport type PickerSdkErrorCode = (typeof SDK_ERROR_CODES)[number];\n\nexport type PickerErrorCode = PickerServerErrorCode | PickerSdkErrorCode;\n\n/**\n * English messages for the developer console. The picker page shows its own\n * translated text to the person using it.\n */\nexport const ERROR_MESSAGES: Record<PickerErrorCode, string> = {\n origin_denied:\n 'This page is not allowed to open the picker: the integration does not exist or does not list this origin.',\n feature_unavailable: 'The asset picker is not available on this workspace.',\n integration_suspended: 'This integration is suspended.',\n integration_unavailable:\n 'This integration cannot be used: its profile or publication channel is missing or disabled.',\n insert_failed: 'The selected assets could not be inserted.',\n auth_required: 'The picker cannot run in an iframe of this page: the person continues in a separate window.',\n guest_access_unavailable: 'This integration does not allow access without an account.',\n popup_blocked: 'The browser blocked the picker window. Call open() from a click handler.',\n invalid_options: 'Invalid picker options.',\n};\n\n/**\n * Errors after which the person can go on in the open picker: `open()`\n * reports them through `onError` and keeps its promise pending. After\n * `insert_failed` the person can insert again; after\n * `guest_access_unavailable` the picker still offers to sign in.\n */\nexport const RECOVERABLE_ERROR_CODES: readonly PickerServerErrorCode[] = ['insert_failed', 'guest_access_unavailable'];\n\nexport function isServerErrorCode(value: unknown): value is PickerServerErrorCode {\n return (SERVER_ERROR_CODES as readonly unknown[]).includes(value);\n}\n\n/** Error passed to `onError` and used to reject the promise of `open()`. */\nexport class FreedamPickerError extends Error {\n readonly code: PickerErrorCode;\n\n constructor(code: PickerErrorCode, detail?: string) {\n super(detail === undefined ? ERROR_MESSAGES[code] : `${ERROR_MESSAGES[code]} ${detail}`);\n this.name = 'FreedamPickerError';\n this.code = code;\n }\n}\n","/**\n * Protocol between the picker window (served on the workspace host) and the\n * host page that opened it.\n *\n * This file is the only definition of the protocol: the SDK imports it, and\n * the Freedam application imports it through the `@freedam/picker-protocol`\n * alias (tsconfig.json and vite.config.ts) for the picker page's side.\n *\n * The protocol version is independent of the SDK version. A receiver ignores\n * any message `type` it does not know, so adding a message is compatible.\n */\nimport type { PickerServerErrorCode } from './errors';\nimport type { PickerAsset, PickerAssetType } from './payload';\n\nexport const PROTOCOL_VERSION = 1;\n\nexport type PickerProtocolVersion = typeof PROTOCOL_VERSION;\n\n/** Value of `source` in every message, to discard other scripts' messages quickly. */\nexport const MESSAGE_SOURCE = 'freedam-picker';\n\n/** Path of the picker page on the workspace host, followed by the integration key. */\nexport const PICKER_PATH = '/picker/';\n\n/** Most assets one insertion can return. */\nexport const MAX_SELECTION = 50;\n\n/** Longest search text, as for `q` in `GET /api/v1/assets`. */\nexport const QUERY_MAX_LENGTH = 500;\n\n/** Longest opening URL: nginx answers 414 past its header buffers. */\nexport const OPEN_URL_MAX_LENGTH = 6000;\n\nexport const PICKER_MODES = ['popup', 'modal', 'inline', 'tab'] as const;\n\n/**\n * How the picker opens: in a popup window on the workspace host (`popup`),\n * in an iframe over the host page (`modal`, version 1.1), in an iframe\n * inside an element of the host page (`inline`, version 1.1) or in a new\n * tab of the browser (`tab`, version 1.2).\n */\nexport type PickerMode = (typeof PICKER_MODES)[number];\n\n/** Modes that show the picker in a window of its own, which posts to `window.opener`. */\nexport type PickerWindowMode = Extract<PickerMode, 'popup' | 'tab'>;\n\n/** Modes that show the picker in an iframe of the host page. */\nexport type PickerFrameMode = Exclude<PickerMode, PickerWindowMode>;\n\n/** Smallest height of the inline iframe, in CSS pixels. */\nexport const INLINE_MIN_HEIGHT = 480;\n\n/** Below this viewport width, in CSS pixels, the modal fills the screen. */\nexport const MODAL_FULL_SCREEN_BELOW = 640;\n\n/** Values of the `types` option (version 1.2): the payload's asset types. */\nexport const PICKER_ASSET_TYPES = ['image', 'video', 'document'] as const satisfies readonly PickerAssetType[];\n\nexport const SELECT_MODES = ['single', 'multiple'] as const;\n\nexport type PickerSelectMode = (typeof SELECT_MODES)[number];\n\n/** Interface languages of Freedam. */\nexport const PICKER_LOCALES = ['en', 'fr', 'de', 'es', 'pt-BR', 'zh-CN'] as const;\n\nexport type PickerLocale = (typeof PICKER_LOCALES)[number];\n\nexport const PICKER_THEMES = ['light', 'dark', 'auto'] as const;\n\nexport type PickerTheme = (typeof PICKER_THEMES)[number];\n\n/** Sort fields, as for `sort_by` in `GET /api/v1/assets`. */\nexport const SORT_FIELDS = ['created_at', 'updated_at', 'title', 'original_filename', 'size', 'relevance'] as const;\n\nexport type SortField = (typeof SORT_FIELDS)[number];\n\nexport const SORT_DIRECTIONS = ['asc', 'desc'] as const;\n\nexport type SortDirection = (typeof SORT_DIRECTIONS)[number];\n\n/**\n * One condition of a filter tree, as for `filters` in `GET /api/v1/assets`,\n * e.g. `{ type: 'filter', field: 'asset.file_category', operator: 'equals', value: 'Image' }`.\n * The server validates fields, operators and values.\n */\nexport interface RuleFilter {\n type: 'filter';\n field: string;\n operator: string;\n value?: unknown;\n}\n\nexport interface RuleGroup {\n type: 'group';\n operator: 'and' | 'or';\n rules: RuleTree[];\n}\n\nexport type RuleTree = RuleFilter | RuleGroup;\n\n/**\n * Query parameters of the opening URL `https://{workspace}/picker/{integration}`.\n * None of them is a secret. The picker page keeps them for the whole session\n * of its window, including the reload after the person signs in.\n */\nexport interface PickerOpenQuery {\n protocol: `${PickerProtocolVersion}`;\n /** Origin of the host page, checked by the server against the integration's origins. */\n origin: string;\n /** Random nonce, 22 base64url characters, repeated in every message. */\n channel: string;\n /** The server also reads it to start the iframe's own session (`modal`, `inline`). */\n mode: PickerMode;\n select: PickerSelectMode;\n /** 1 to MAX_SELECTION, only with `select=multiple`. */\n max?: string;\n q?: string;\n /** RuleTree as JSON. */\n filters?: string;\n sort_by?: SortField;\n sort_direction?: SortDirection;\n /** Comma-separated GAIDs; the picker ignores those the person cannot see. */\n preselected?: string;\n /**\n * Comma-separated asset types, version 1.2. The picker offers only these\n * among the integration's own types; a type the integration does not\n * offer is ignored.\n */\n types?: string;\n /** Key of the integration's access without an account (`fdpk_…`), version 1.1. */\n public_key?: string;\n locale?: PickerLocale;\n theme?: PickerTheme;\n}\n\n/** Fields of every message. */\nexport interface PickerMessageBase {\n source: typeof MESSAGE_SOURCE;\n protocol: PickerProtocolVersion;\n channel: string;\n}\n\n/** The picker page is interactive. Sent on every page load, including after sign-in. */\nexport interface PickerReadyMessage extends PickerMessageBase {\n type: 'ready';\n integration: string;\n}\n\n/** The insertion succeeded; the picker window closes next. */\nexport interface PickerSelectMessage extends PickerMessageBase {\n type: 'select';\n assets: PickerAsset[];\n}\n\n/** The person pressed Cancel; the picker window closes next. */\nexport interface PickerCancelMessage extends PickerMessageBase {\n type: 'cancel';\n}\n\n/**\n * The picker in an iframe shows its sign-in screen (version 1.1): the page\n * loaded and waits for the person, and `ready` follows once they have\n * signed in or continued without an account.\n */\nexport interface PickerSignInMessage extends PickerMessageBase {\n type: 'sign_in';\n}\n\n/**\n * Height the picker's content needs, in CSS pixels (version 1.1). The SDK\n * applies it to the inline iframe, never below INLINE_MIN_HEIGHT, and\n * ignores it in the other modes.\n */\nexport interface PickerResizeMessage extends PickerMessageBase {\n type: 'resize';\n height: number;\n}\n\n/** An error the picker page also shows to the person; the window stays open. */\nexport interface PickerErrorMessage extends PickerMessageBase {\n type: 'error';\n code: PickerServerErrorCode;\n}\n\nexport type PickerToHostMessage =\n | PickerReadyMessage\n | PickerSelectMessage\n | PickerCancelMessage\n | PickerErrorMessage\n | PickerSignInMessage\n | PickerResizeMessage;\n\nexport type PickerToHostMessageType = PickerToHostMessage['type'];\n\n/**\n * The host page removes the picker's iframe (version 1.1): sent by the SDK\n * to the iframe, with the workspace origin as `targetOrigin`, just before it\n * removes it after an insertion or a cancellation, or when the person\n * closes the modal. The picker page does not answer it.\n */\nexport interface HostCloseMessage extends PickerMessageBase {\n type: 'close';\n}\n\n/** Messages of the host page to the picker; only the iframe modes send one. */\nexport type HostToPickerMessage = HostCloseMessage;\n\n/**\n * What the picker page needs to post to its host. Built by the server after\n * it has checked the origin, never read from the page URL in the browser.\n */\nexport interface PickerHostContext {\n /** Exact origin the host declared and the server accepted; always the `targetOrigin`. */\n origin: string;\n channel: string;\n protocol: PickerProtocolVersion;\n}\n","import { FreedamPickerError } from './errors';\nimport type { PickerAsset, PickerAssetType } from './payload';\nimport {\n MAX_SELECTION,\n OPEN_URL_MAX_LENGTH,\n PICKER_ASSET_TYPES,\n PICKER_LOCALES,\n PICKER_MODES,\n PICKER_PATH,\n PICKER_THEMES,\n PROTOCOL_VERSION,\n QUERY_MAX_LENGTH,\n SELECT_MODES,\n SORT_DIRECTIONS,\n SORT_FIELDS,\n type PickerLocale,\n type PickerMode,\n type PickerSelectMode,\n type PickerTheme,\n type RuleTree,\n type SortDirection,\n type SortField,\n} from './protocol';\n\nexport interface PickerOptions {\n /** Host of the Freedam workspace, without scheme or path: `acme.example.com`. HTTPS is always used. */\n workspace: string;\n /** Public key of the integration, shown in the workspace's integration settings. */\n integration: string;\n /**\n * Key of the integration's access without a Freedam account (`fdpk_…`),\n * shown in the integration's settings when the access is on. With it,\n * visitors without an account can browse and insert the integration's\n * assets. Not a secret.\n */\n publicKey?: string;\n /** Default `single`. */\n select?: PickerSelectMode;\n /** Most assets the person can choose, 1 to 50, with `select: 'multiple'`. */\n max?: number;\n /** Initial search text, 500 characters at most. */\n q?: string;\n /** Initial filters, applied on top of the integration's own rules. */\n filters?: RuleTree;\n sort?: { by: SortField; direction?: SortDirection };\n /** GAIDs selected when the picker opens; GAIDs the person cannot see are ignored. */\n preselected?: string[];\n /**\n * Asset types the picker offers, among the integration's own: `['image']`\n * for an image field. A type the integration does not offer is ignored.\n * Default: every type of the integration.\n */\n types?: PickerAssetType[];\n /** Interface language; default: the person's own setting. */\n locale?: PickerLocale;\n theme?: PickerTheme;\n /**\n * Default `popup`: a window on the workspace host. `tab` opens it in a\n * new tab of the browser instead. `modal` shows the picker in an iframe\n * over the page (full screen below 640 pixels wide); `inline` shows it\n * in an iframe inside `container`. When the browser does not let the\n * iframe sign in, the SDK offers to continue in a window.\n */\n mode?: PickerMode;\n /** Element that receives the picker's iframe, required with `mode: 'inline'` and only then. */\n container?: HTMLElement;\n onReady?: () => void;\n onSelect?: (assets: PickerAsset[]) => void;\n onCancel?: () => void;\n onError?: (error: FreedamPickerError) => void;\n onClose?: () => void;\n}\n\nconst CALLBACKS = ['onReady', 'onSelect', 'onCancel', 'onError', 'onClose'] as const;\n\n/** `fdpk_` and 32 base-62 characters. */\nconst PUBLIC_KEY_PATTERN = /^fdpk_[A-Za-z0-9]{32}$/;\n\n/** Labels of host names, then an optional port. No scheme, path, user or query. */\nconst WORKSPACE_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*(?::\\d{1,5})?$/i;\n\nfunction invalid(detail: string): FreedamPickerError {\n return new FreedamPickerError('invalid_options', detail);\n}\n\nfunction isOneOf<T extends string>(values: readonly T[], value: unknown): value is T {\n return (values as readonly unknown[]).includes(value);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isElement(value: unknown): value is HTMLElement {\n return typeof HTMLElement !== 'undefined' && value instanceof HTMLElement;\n}\n\n/** Throws FreedamPickerError `invalid_options` naming the first invalid option. */\nexport function validateOptions(options: unknown): asserts options is PickerOptions {\n if (!isPlainObject(options)) {\n throw invalid('open() expects an options object.');\n }\n\n if (typeof options.workspace !== 'string' || !WORKSPACE_PATTERN.test(options.workspace) || !isValidHost(options.workspace)) {\n throw invalid('`workspace` must be the host of the workspace, without scheme or path, e.g. \"acme.example.com\".');\n }\n\n if (typeof options.integration !== 'string' || !/^\\S+$/.test(options.integration)) {\n throw invalid('`integration` must be the key of the integration, without spaces.');\n }\n\n if (options.publicKey !== undefined && (typeof options.publicKey !== 'string' || !PUBLIC_KEY_PATTERN.test(options.publicKey))) {\n throw invalid('`publicKey` must be the key of the access without an account: \"fdpk_\" followed by 32 letters and digits.');\n }\n\n if (options.select !== undefined && !isOneOf(SELECT_MODES, options.select)) {\n throw invalid('`select` must be \"single\" or \"multiple\".');\n }\n\n if (options.max !== undefined) {\n if (options.select !== 'multiple') {\n throw invalid('`max` requires `select: \"multiple\"`.');\n }\n if (typeof options.max !== 'number' || !Number.isInteger(options.max) || options.max < 1 || options.max > MAX_SELECTION) {\n throw invalid(`\\`max\\` must be an integer from 1 to ${MAX_SELECTION}.`);\n }\n }\n\n if (options.q !== undefined && (typeof options.q !== 'string' || options.q.length > QUERY_MAX_LENGTH)) {\n throw invalid(`\\`q\\` must be a string of ${QUERY_MAX_LENGTH} characters at most.`);\n }\n\n if (options.filters !== undefined) {\n if (!isPlainObject(options.filters)) {\n throw invalid('`filters` must be a filter or group object.');\n }\n try {\n JSON.stringify(options.filters);\n } catch {\n throw invalid('`filters` must be serializable to JSON.');\n }\n }\n\n if (options.sort !== undefined) {\n if (!isPlainObject(options.sort) || !isOneOf(SORT_FIELDS, options.sort.by)) {\n throw invalid(`\\`sort.by\\` must be one of ${SORT_FIELDS.join(', ')}.`);\n }\n if (options.sort.direction !== undefined && !isOneOf(SORT_DIRECTIONS, options.sort.direction)) {\n throw invalid('`sort.direction` must be \"asc\" or \"desc\".');\n }\n }\n\n if (options.preselected !== undefined) {\n const preselected = options.preselected;\n if (!Array.isArray(preselected) || !preselected.every((gaid) => typeof gaid === 'string' && gaid !== '' && !gaid.includes(','))) {\n throw invalid('`preselected` must be an array of GAIDs.');\n }\n }\n\n if (options.types !== undefined) {\n const types = options.types;\n if (!Array.isArray(types) || types.length === 0 || !types.every((type) => isOneOf(PICKER_ASSET_TYPES, type))) {\n throw invalid(`\\`types\\` must be a non-empty array of ${PICKER_ASSET_TYPES.join(', ')}.`);\n }\n }\n\n if (options.locale !== undefined && !isOneOf(PICKER_LOCALES, options.locale)) {\n throw invalid(`\\`locale\\` must be one of ${PICKER_LOCALES.join(', ')}.`);\n }\n\n if (options.theme !== undefined && !isOneOf(PICKER_THEMES, options.theme)) {\n throw invalid('`theme` must be \"light\", \"dark\" or \"auto\".');\n }\n\n if (options.mode !== undefined && !isOneOf(PICKER_MODES, options.mode)) {\n throw invalid('`mode` must be \"popup\", \"tab\", \"modal\" or \"inline\".');\n }\n\n if (options.mode === 'inline') {\n if (!isElement(options.container) || !options.container.isConnected) {\n throw invalid('`mode: \"inline\"` requires `container`, an element of the page.');\n }\n } else if (options.container !== undefined) {\n throw invalid('`container` requires `mode: \"inline\"`.');\n }\n\n for (const name of CALLBACKS) {\n if (options[name] !== undefined && typeof options[name] !== 'function') {\n throw invalid(`\\`${name}\\` must be a function.`);\n }\n }\n}\n\n/** The URL parser also rejects what the pattern lets through, such as a port above 65535. */\nfunction isValidHost(workspace: string): boolean {\n try {\n workspaceOrigin(workspace);\n\n return true;\n } catch {\n return false;\n }\n}\n\n/** Origin of the workspace, the only origin whose messages the SDK accepts. */\nexport function workspaceOrigin(workspace: string): string {\n return new URL(`https://${workspace}`).origin;\n}\n\n/** Origin of the host page; throws `invalid_options` for a page without an HTTP(S) origin (file://, sandboxed). */\nexport function hostOrigin(origin: string): string {\n if (!/^https?:\\/\\//.test(origin)) {\n throw invalid('The host page must be served over HTTP or HTTPS.');\n }\n\n return origin;\n}\n\n/** 128 random bits as 22 base64url characters. */\nexport function createChannel(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * URL of the picker page, with the query parameters of the protocol. `mode`\n * replaces the option's mode: the SDK opens a window with the same options\n * when an iframe cannot be used.\n */\nexport function buildOpenUrl(options: PickerOptions, origin: string, channel: string, mode: PickerMode = options.mode ?? 'popup'): string {\n const url = new URL(`${PICKER_PATH}${encodeURIComponent(options.integration)}`, workspaceOrigin(options.workspace));\n const params = url.searchParams;\n\n params.set('protocol', String(PROTOCOL_VERSION));\n params.set('origin', origin);\n params.set('channel', channel);\n params.set('mode', mode);\n params.set('select', options.select ?? 'single');\n\n if (options.max !== undefined) {\n params.set('max', String(options.max));\n }\n if (options.publicKey !== undefined) {\n params.set('public_key', options.publicKey);\n }\n if (options.q !== undefined && options.q !== '') {\n params.set('q', options.q);\n }\n if (options.filters !== undefined) {\n params.set('filters', JSON.stringify(options.filters));\n }\n if (options.sort !== undefined) {\n params.set('sort_by', options.sort.by);\n if (options.sort.direction !== undefined) {\n params.set('sort_direction', options.sort.direction);\n }\n }\n if (options.preselected !== undefined && options.preselected.length > 0) {\n params.set('preselected', options.preselected.join(','));\n }\n if (options.types !== undefined) {\n params.set('types', [...new Set(options.types)].join(','));\n }\n if (options.locale !== undefined) {\n params.set('locale', options.locale);\n }\n if (options.theme !== undefined) {\n params.set('theme', options.theme);\n }\n\n const href = url.toString();\n if (href.length > OPEN_URL_MAX_LENGTH) {\n throw invalid(`The picker URL would exceed ${OPEN_URL_MAX_LENGTH} characters: shorten \\`filters\\`, \\`q\\` or \\`preselected\\`.`);\n }\n\n return href;\n}\n","/**\n * The picker in an iframe of the host page: over the page (`modal`) or\n * inside an element the host gives (`inline`). The iframe has no `sandbox`\n * attribute: its sign-in screen opens a popup on the workspace.\n *\n * When the iframe cannot be used (the browser refuses its session cookie,\n * or the picker page never answers), the SDK replaces it with a short text\n * and an \"Open Freedam\" button that opens the picker in a window. The\n * window needs the click: browsers block a window opened without one.\n *\n * Styles are set through the CSSOM, never with a `<style>` element or a\n * `style` attribute, so a Content-Security-Policy without `unsafe-inline`\n * in `style-src` does not block them.\n */\nimport { INLINE_MIN_HEIGHT, MESSAGE_SOURCE, MODAL_FULL_SCREEN_BELOW, PICKER_LOCALES, PROTOCOL_VERSION, type HostCloseMessage, type PickerFrameMode, type PickerLocale } from './protocol';\nimport type { PickerSurface } from './surface';\n\n/** Largest size of the modal on a wide screen, in CSS pixels. */\nexport const MODAL_MAX_WIDTH = 1100;\n\nexport const MODAL_MAX_HEIGHT = 760;\n\n/** Largest height the picker can ask for the inline iframe, in CSS pixels. */\nexport const INLINE_MAX_HEIGHT = 10000;\n\n/** Above the host page's own layers. */\nconst Z_INDEX = '2147483000';\n\ninterface FrameText {\n message: string;\n open: string;\n cancel: string;\n close: string;\n}\n\n/** The SDK's only text, shown by the modal and by the fallback that replaces an unusable iframe. */\nexport const FRAME_TEXT: Record<PickerLocale, FrameText> = {\n en: {\n message: 'Freedam cannot be shown in this page. Continue in a separate window.',\n open: 'Open Freedam',\n cancel: 'Cancel',\n close: 'Close',\n },\n fr: {\n message: 'Freedam ne peut pas s’afficher dans cette page. Continuez dans une fenêtre séparée.',\n open: 'Ouvrir Freedam',\n cancel: 'Annuler',\n close: 'Fermer',\n },\n de: {\n message: 'Freedam kann auf dieser Seite nicht angezeigt werden. Fahren Sie in einem separaten Fenster fort.',\n open: 'Freedam öffnen',\n cancel: 'Abbrechen',\n close: 'Schließen',\n },\n es: {\n message: 'Freedam no se puede mostrar en esta página. Continúe en una ventana aparte.',\n open: 'Abrir Freedam',\n cancel: 'Cancelar',\n close: 'Cerrar',\n },\n 'pt-BR': {\n message: 'O Freedam não pode ser exibido nesta página. Continue em uma janela separada.',\n open: 'Abrir o Freedam',\n cancel: 'Cancelar',\n close: 'Fechar',\n },\n 'zh-CN': {\n message: '无法在此页面中显示 Freedam。请在单独的窗口中继续。',\n open: '打开 Freedam',\n cancel: '取消',\n close: '关闭',\n },\n};\n\n/**\n * Language of the SDK's text: the `locale` option, else the host page's\n * `lang`, else the browser's language, matched on the language when the\n * region differs (`fr-CA` gives `fr`); English otherwise.\n */\nexport function frameLocale(locale?: PickerLocale): PickerLocale {\n const candidates = [locale, document.documentElement.lang, typeof navigator === 'undefined' ? undefined : navigator.language];\n\n for (const candidate of candidates) {\n if (typeof candidate !== 'string' || candidate === '') {\n continue;\n }\n const exact = PICKER_LOCALES.find((supported) => supported.toLowerCase() === candidate.toLowerCase());\n if (exact !== undefined) {\n return exact;\n }\n const language = candidate.split('-')[0].toLowerCase();\n const sameLanguage = PICKER_LOCALES.find((supported) => supported.split('-')[0] === language);\n if (sameLanguage !== undefined) {\n return sameLanguage;\n }\n }\n\n return 'en';\n}\n\nexport interface FrameOptions {\n url: string;\n channel: string;\n /** Origin of the workspace: the `targetOrigin` of the `close` message. */\n origin: string;\n mode: PickerFrameMode;\n /** Required with `inline`. */\n container?: HTMLElement;\n locale?: PickerLocale;\n /** The person closed the modal or pressed Cancel in the fallback. */\n onDismiss: () => void;\n /** The person pressed \"Open Freedam\" in the fallback; runs in the click. */\n onOpenWindow: () => void;\n}\n\nexport interface FrameSurface extends PickerSurface {\n readonly mode: PickerFrameMode;\n /**\n * Removes the iframe or its fallback. `restoreFocus: false` leaves the\n * focus alone, when the picker continues in a window that has it.\n */\n close(restoreFocus?: boolean): void;\n /** Replaces the iframe with the text and the \"Open Freedam\" button. */\n showFallback(): void;\n /** Height the picker asked for; applied to the inline iframe only. */\n resize(height: number): void;\n}\n\ntype Style = Partial<Record<keyof CSSStyleDeclaration, string>>;\n\nfunction element<K extends keyof HTMLElementTagNameMap>(tag: K, style: Style = {}): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n Object.assign(node.style, style);\n\n return node;\n}\n\nfunction button(label: string, primary: boolean): HTMLButtonElement {\n const node = element('button', {\n font: 'inherit',\n fontWeight: '600',\n padding: '10px 18px',\n borderRadius: '8px',\n cursor: 'pointer',\n border: primary ? '1px solid #111827' : '1px solid #d1d5db',\n background: primary ? '#111827' : '#ffffff',\n color: primary ? '#ffffff' : '#111827',\n });\n node.type = 'button';\n node.textContent = label;\n\n return node;\n}\n\n/**\n * Shows the picker in an iframe. Must run in the same task as the person's\n * click when `mode` is `modal`, so the focus moves to the picker.\n */\nexport function openFrame(options: FrameOptions): FrameSurface {\n const text = FRAME_TEXT[frameLocale(options.locale)];\n const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n const cleanups: Array<() => void> = [];\n let closed = false;\n\n const iframe = element('iframe', {\n display: 'block',\n width: '100%',\n height: '100%',\n border: '0',\n background: '#ffffff',\n });\n iframe.src = options.url;\n iframe.name = `freedam-picker-${options.channel}`;\n iframe.title = 'Freedam';\n iframe.setAttribute('data-freedam-picker', options.mode);\n\n // `slot` holds the iframe, then the fallback that replaces it.\n let root: HTMLElement;\n let slot: HTMLElement;\n\n if (options.mode === 'modal') {\n root = element('div', {\n position: 'fixed',\n top: '0',\n right: '0',\n bottom: '0',\n left: '0',\n zIndex: Z_INDEX,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n boxSizing: 'border-box',\n background: 'rgba(17, 24, 39, 0.6)',\n });\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-label', 'Freedam');\n root.setAttribute('data-freedam-picker-overlay', '');\n\n slot = element('div', {\n position: 'relative',\n width: '100%',\n height: '100%',\n overflow: 'hidden',\n background: '#ffffff',\n boxShadow: '0 24px 48px rgba(0, 0, 0, 0.3)',\n });\n\n const close = element('button', {\n position: 'absolute',\n top: '6px',\n right: '8px',\n width: '32px',\n height: '32px',\n padding: '0',\n border: '0',\n borderRadius: '16px',\n background: 'transparent',\n color: '#ffffff',\n fontSize: '24px',\n lineHeight: '32px',\n cursor: 'pointer',\n });\n close.type = 'button';\n close.textContent = '×';\n close.setAttribute('aria-label', text.close);\n close.setAttribute('data-freedam-picker-close', '');\n close.addEventListener('click', () => options.onDismiss());\n\n root.append(slot, close);\n\n // Full screen on a narrow viewport, with a bar for the close button.\n const fullScreen = typeof window.matchMedia === 'function' ? window.matchMedia(`(max-width: ${MODAL_FULL_SCREEN_BELOW - 0.02}px)`) : null;\n const layout = (): void => {\n const narrow = fullScreen?.matches === true;\n Object.assign(root.style, { padding: narrow ? '44px 0 0' : '48px 24px 24px' });\n Object.assign(slot.style, {\n maxWidth: narrow ? 'none' : `${MODAL_MAX_WIDTH}px`,\n maxHeight: narrow ? 'none' : `${MODAL_MAX_HEIGHT}px`,\n borderRadius: narrow ? '0' : '12px',\n });\n };\n layout();\n if (fullScreen !== null) {\n fullScreen.addEventListener('change', layout);\n cleanups.push(() => fullScreen.removeEventListener('change', layout));\n }\n\n // Escape reaches this page only while the focus is on it (the\n // close button, the fallback), not inside the picker's iframe.\n // Tab keeps the focus inside the modal.\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') {\n options.onDismiss();\n\n return;\n }\n if (event.key !== 'Tab') {\n return;\n }\n const focusable = Array.from(root.querySelectorAll<HTMLElement>('button, iframe'));\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const current = document.activeElement;\n if (first === undefined || last === undefined) {\n return;\n }\n if (!(current instanceof Node) || !root.contains(current)) {\n event.preventDefault();\n first.focus();\n } else if (event.shiftKey && current === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && current === last) {\n event.preventDefault();\n first.focus();\n }\n };\n document.addEventListener('keydown', onKeyDown);\n cleanups.push(() => document.removeEventListener('keydown', onKeyDown));\n\n const html = document.documentElement;\n const overflow = html.style.overflow;\n html.style.overflow = 'hidden';\n cleanups.push(() => {\n html.style.overflow = overflow;\n });\n\n slot.append(iframe);\n document.body.append(root);\n } else {\n root = element('div', {\n display: 'block',\n width: '100%',\n height: '100%',\n minHeight: `${INLINE_MIN_HEIGHT}px`,\n });\n root.setAttribute('data-freedam-picker-inline', '');\n slot = root;\n iframe.style.minHeight = `${INLINE_MIN_HEIGHT}px`;\n slot.append(iframe);\n (options.container as HTMLElement).append(root);\n }\n\n if (options.mode === 'modal') {\n iframe.focus();\n }\n\n const fallbackButton = (): HTMLButtonElement | null => slot.querySelector('[data-freedam-picker-open]');\n\n return {\n mode: options.mode,\n target: () => (iframe.isConnected ? iframe.contentWindow : null),\n isClosed: () => closed || !root.isConnected,\n focus: () => (fallbackButton() ?? iframe).focus(),\n close: (restoreFocus = true) => {\n if (closed) {\n return;\n }\n closed = true;\n const target = iframe.isConnected ? iframe.contentWindow : null;\n if (target !== null) {\n const message: HostCloseMessage = { source: MESSAGE_SOURCE, protocol: PROTOCOL_VERSION, channel: options.channel, type: 'close' };\n target.postMessage(message, options.origin);\n }\n root.remove();\n for (const cleanup of cleanups) {\n cleanup();\n }\n if (restoreFocus && options.mode === 'modal' && previousFocus !== null && previousFocus.isConnected) {\n previousFocus.focus();\n }\n },\n showFallback: () => {\n if (closed || fallbackButton() !== null) {\n return;\n }\n const panel = element('div', {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '16px',\n boxSizing: 'border-box',\n width: '100%',\n height: '100%',\n minHeight: options.mode === 'inline' ? `${INLINE_MIN_HEIGHT}px` : '0',\n padding: '24px',\n background: '#ffffff',\n color: '#111827',\n fontFamily: 'system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif',\n fontSize: '15px',\n lineHeight: '1.5',\n textAlign: 'center',\n });\n panel.setAttribute('data-freedam-picker-fallback', '');\n\n const message = element('p', { margin: '0', maxWidth: '420px' });\n message.textContent = text.message;\n\n const actions = element('div', { display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: '8px' });\n const open = button(text.open, true);\n open.setAttribute('data-freedam-picker-open', '');\n open.addEventListener('click', () => options.onOpenWindow());\n const cancel = button(text.cancel, false);\n cancel.setAttribute('data-freedam-picker-cancel', '');\n cancel.addEventListener('click', () => options.onDismiss());\n actions.append(open, cancel);\n\n panel.append(message, actions);\n iframe.remove();\n slot.append(panel);\n if (options.mode === 'modal') {\n open.focus();\n }\n },\n resize: (height: number) => {\n if (options.mode !== 'inline' || !Number.isFinite(height)) {\n return;\n }\n iframe.style.height = `${Math.round(Math.min(INLINE_MAX_HEIGHT, Math.max(INLINE_MIN_HEIGHT, height)))}px`;\n root.style.height = 'auto';\n },\n };\n}\n","import type { PickerSurface } from './surface';\n\nexport const POPUP_WIDTH = 1100;\n\nexport const POPUP_HEIGHT = 760;\n\n/**\n * The picker's windows: a popup (`mode: 'popup'`) or a tab (`mode: 'tab'`,\n * version 1.2). Both must open in the same task as the person's click,\n * with no `await` before it, or browsers block them, and both keep\n * `window.opener` (no `noopener`): the picker page posts its messages to\n * it. Each returns null when the browser blocked the window.\n */\n\n/** Opens the picker in a popup centred on the host window. */\nexport function openPopup(url: string, channel: string): PickerSurface | null {\n const width = Math.min(POPUP_WIDTH, window.screen.availWidth || POPUP_WIDTH);\n const height = Math.min(POPUP_HEIGHT, window.screen.availHeight || POPUP_HEIGHT);\n const left = Math.round(window.screenX + (window.outerWidth - width) / 2);\n const top = Math.round(window.screenY + (window.outerHeight - height) / 2);\n\n return windowSurface(\n window.open(url, `freedam-picker-${channel}`, `popup=yes,width=${width},height=${height},left=${left},top=${top}`),\n );\n}\n\n/** Opens the picker in a new tab: without window features, desktop browsers open a tab. */\nexport function openTab(url: string, channel: string): PickerSurface | null {\n return windowSurface(window.open(url, `freedam-picker-${channel}`));\n}\n\nfunction windowSurface(opened: Window | null): PickerSurface | null {\n if (!opened) {\n return null;\n }\n\n return {\n target: () => opened,\n isClosed: () => opened.closed,\n focus: () => opened.focus(),\n close: () => opened.close(),\n };\n}\n","import { FreedamPickerError, RECOVERABLE_ERROR_CODES, isServerErrorCode } from './errors';\nimport { buildOpenUrl, createChannel, hostOrigin, validateOptions, workspaceOrigin, type PickerOptions } from './options';\nimport type { PickerAsset } from './payload';\nimport { openFrame, type FrameSurface } from './frame';\nimport { openPopup, openTab } from './popup';\nimport { MESSAGE_SOURCE, PROTOCOL_VERSION, type PickerToHostMessage } from './protocol';\nimport type { PickerSurface } from './surface';\n\n/** How often the SDK checks whether the person closed the picker window. */\nexport const CLOSE_POLL_INTERVAL_MS = 500;\n\n/** Delay after `select` or `cancel` before the SDK closes a window that did not close itself. */\nexport const CLOSE_GRACE_MS = 1000;\n\n/**\n * How long the SDK waits for the picker in an iframe to send its first\n * message (`ready`, `sign_in` or `error`) before it offers to continue in a\n * window: the page may be refused by the browser or never load.\n */\nexport const READY_TIMEOUT_MS = 15000;\n\ntype PickerResult = PickerAsset[] | null;\n\n/** The opening in progress; `surface()` is the iframe, or the window that replaced it. */\nlet active: { surface: () => PickerSurface; promise: Promise<PickerResult> } | null = null;\n\n/** Whether the page has the transient activation of a click, also given by a click inside the picker's iframe. */\nfunction hasUserActivation(): boolean {\n const activation = (navigator as Navigator & { userActivation?: { isActive: boolean } }).userActivation;\n\n return activation?.isActive === true;\n}\n\n/**\n * Calls a host callback. An exception thrown by the host is reported\n * asynchronously, so it reaches the console without breaking the SDK.\n */\nfunction notify<A extends unknown[]>(callback: ((...args: A) => void) | undefined, ...args: A): void {\n if (typeof callback !== 'function') {\n return;\n }\n try {\n callback(...args);\n } catch (error) {\n setTimeout(() => {\n throw error;\n });\n }\n}\n\nfunction isPickerMessage(data: unknown, channel: string): data is PickerToHostMessage {\n if (typeof data !== 'object' || data === null) {\n return false;\n }\n const message = data as Record<string, unknown>;\n\n return (\n message.source === MESSAGE_SOURCE &&\n message.protocol === PROTOCOL_VERSION &&\n message.channel === channel &&\n typeof message.type === 'string'\n );\n}\n\n/**\n * Opens the picker and resolves with the inserted assets, or with `null`\n * when the person cancels or closes it.\n *\n * Call it from a click handler, with no `await` before it: browsers block\n * windows opened outside a user gesture. Invalid options reject with\n * `invalid_options` before anything opens. While a picker of this page is\n * open, another call brings it to the front and returns the same promise.\n *\n * In the `popup` and `tab` modes the picker runs in a window of its own,\n * a popup or a new tab of the browser.\n *\n * In the `modal` and `inline` modes the picker runs in an iframe. When the\n * iframe cannot sign in (`auth_required`) or the picker page sends nothing\n * within READY_TIMEOUT_MS, the iframe gives way to an \"Open Freedam\"\n * button that opens the picker in a window with the same options; the\n * promise then settles with the outcome of that window.\n */\nexport function open(options: PickerOptions): Promise<PickerResult> {\n if (active !== null && !active.surface().isClosed()) {\n active.surface().focus();\n\n return active.promise;\n }\n\n let url: string;\n let windowUrl: string;\n let origin: string;\n let channel: string;\n try {\n validateOptions(options);\n origin = workspaceOrigin(options.workspace);\n channel = createChannel();\n const pageOrigin = hostOrigin(window.location.origin);\n url = buildOpenUrl(options, pageOrigin, channel);\n windowUrl = buildOpenUrl(options, pageOrigin, channel, 'popup');\n } catch (error) {\n if (!(error instanceof FreedamPickerError)) {\n throw error;\n }\n notify((options as Partial<PickerOptions> | null)?.onError, error);\n\n return Promise.reject(error);\n }\n\n const mode = options.mode ?? 'popup';\n let surface: PickerSurface;\n // The iframe while it shows the picker or its fallback; null in a window.\n let frame: FrameSurface | null = null;\n const controls = { dismiss: (): void => undefined, openWindow: (): void => undefined };\n\n if (mode === 'popup' || mode === 'tab') {\n const opened = mode === 'tab' ? openTab(url, channel) : openPopup(url, channel);\n if (opened === null) {\n const error = new FreedamPickerError('popup_blocked');\n notify(options.onError, error);\n\n return Promise.reject(error);\n }\n surface = opened;\n } else {\n frame = openFrame({\n url,\n channel,\n origin,\n mode,\n container: options.container,\n locale: options.locale,\n onDismiss: () => controls.dismiss(),\n onOpenWindow: () => controls.openWindow(),\n });\n surface = frame;\n }\n\n const promise = new Promise<PickerResult>((resolve, reject) => {\n let settled = false;\n let readyNotified = false;\n let closeTimer: ReturnType<typeof setTimeout> | undefined;\n let readyTimer: ReturnType<typeof setTimeout> | undefined;\n\n const closeSoon = (): void => {\n if (frame !== null) {\n // An iframe cannot close itself: remove it at once.\n frame.close();\n checkClosed();\n\n return;\n }\n closeTimer = setTimeout(() => {\n if (!surface.isClosed()) {\n surface.close();\n }\n }, CLOSE_GRACE_MS);\n };\n\n const checkClosed = (): void => {\n if (!surface.isClosed()) {\n return;\n }\n // Removes the listeners of an iframe the host page removed itself.\n frame?.close();\n clearInterval(closePoll);\n clearTimeout(closeTimer);\n clearTimeout(readyTimer);\n window.removeEventListener('message', onMessage);\n if (active?.promise === promise) {\n active = null;\n }\n if (!settled) {\n settled = true;\n notify(options.onCancel);\n resolve(null);\n }\n notify(options.onClose);\n };\n\n /** Opens the picker in a window in place of the iframe; false when the browser blocked it. */\n const openWindow = (): boolean => {\n if (frame === null || settled) {\n return false;\n }\n const popup = openPopup(windowUrl, channel);\n if (popup === null) {\n return false;\n }\n clearTimeout(readyTimer);\n // The new window has the focus: do not hand it back to this page.\n frame.close(false);\n frame = null;\n surface = popup;\n\n return true;\n };\n\n controls.dismiss = () => {\n surface.close();\n checkClosed();\n };\n\n controls.openWindow = () => {\n if (!openWindow() && !settled) {\n // Blocked even with the click: nothing more the SDK can do.\n settled = true;\n const error = new FreedamPickerError('popup_blocked');\n notify(options.onError, error);\n reject(error);\n surface.close();\n checkClosed();\n }\n };\n\n const fallBack = (): void => {\n // A click on \"Open in a window\" inside the iframe also activates\n // this page, so the window can open now; otherwise the person\n // clicks the fallback's button.\n if (!(hasUserActivation() && openWindow())) {\n frame?.showFallback();\n }\n };\n\n const onMessage = (event: MessageEvent): void => {\n if (settled || event.origin !== origin || event.source !== surface.target()) {\n return;\n }\n const message = event.data;\n if (!isPickerMessage(message, channel)) {\n return;\n }\n clearTimeout(readyTimer);\n\n switch (message.type) {\n case 'ready':\n if (!readyNotified) {\n readyNotified = true;\n notify(options.onReady);\n }\n break;\n case 'resize':\n if (typeof message.height === 'number') {\n frame?.resize(message.height);\n }\n break;\n case 'select':\n if (!Array.isArray(message.assets)) {\n return;\n }\n settled = true;\n notify(options.onSelect, message.assets);\n resolve(message.assets);\n closeSoon();\n break;\n case 'cancel':\n settled = true;\n notify(options.onCancel);\n resolve(null);\n closeSoon();\n break;\n case 'error': {\n if (!isServerErrorCode(message.code)) {\n return;\n }\n if (message.code === 'auth_required') {\n // Not an error for the host page: the person goes on\n // in a window. A window cannot be sent this code.\n fallBack();\n\n return;\n }\n const error = new FreedamPickerError(message.code);\n notify(options.onError, error);\n // After a recoverable error the person can go on in the\n // open picker: the promise waits for the outcome.\n if (!RECOVERABLE_ERROR_CODES.includes(message.code)) {\n settled = true;\n reject(error);\n }\n break;\n }\n }\n };\n\n const closePoll = setInterval(checkClosed, CLOSE_POLL_INTERVAL_MS);\n\n if (frame !== null) {\n readyTimer = setTimeout(() => {\n if (!settled) {\n frame?.showFallback();\n }\n }, READY_TIMEOUT_MS);\n }\n\n window.addEventListener('message', onMessage);\n });\n\n active = { surface: () => surface, promise };\n\n return promise;\n}\n","/** Version of this SDK, equal to `version` in package.json (checked by tests/version.test.ts). */\nexport const VERSION = '1.2.0';\n"],"mappings":"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","names":["index_exports","__export","ERROR_MESSAGES","FreedamPickerError","INLINE_MIN_HEIGHT","MAX_SELECTION","PICKER_ASSET_TYPES","PICKER_LOCALES","PICKER_MODES","PICKER_THEMES","PROTOCOL_VERSION","SDK_ERROR_CODES","SERVER_ERROR_CODES","SORT_DIRECTIONS","SORT_FIELDS","VERSION","open","__toCommonJS","SERVER_ERROR_CODES","SDK_ERROR_CODES","ERROR_MESSAGES","RECOVERABLE_ERROR_CODES","isServerErrorCode","value","FreedamPickerError","code","detail","PROTOCOL_VERSION","MESSAGE_SOURCE","PICKER_PATH","MAX_SELECTION","PICKER_MODES","INLINE_MIN_HEIGHT","PICKER_ASSET_TYPES","SELECT_MODES","PICKER_LOCALES","PICKER_THEMES","SORT_FIELDS","SORT_DIRECTIONS","CALLBACKS","PUBLIC_KEY_PATTERN","WORKSPACE_PATTERN","invalid","detail","FreedamPickerError","isOneOf","values","value","isPlainObject","isElement","validateOptions","options","isValidHost","SELECT_MODES","SORT_FIELDS","SORT_DIRECTIONS","preselected","gaid","types","type","PICKER_ASSET_TYPES","PICKER_LOCALES","PICKER_THEMES","PICKER_MODES","name","workspace","workspaceOrigin","hostOrigin","origin","createChannel","bytes","binary","byte","buildOpenUrl","channel","mode","url","PICKER_PATH","params","href","MODAL_MAX_WIDTH","MODAL_MAX_HEIGHT","INLINE_MAX_HEIGHT","Z_INDEX","FRAME_TEXT","frameLocale","locale","candidates","candidate","exact","PICKER_LOCALES","supported","language","sameLanguage","element","tag","style","node","button","label","primary","openFrame","options","text","previousFocus","cleanups","closed","iframe","root","slot","close","fullScreen","layout","narrow","onKeyDown","event","focusable","first","last","current","html","overflow","fallbackButton","restoreFocus","target","message","MESSAGE_SOURCE","cleanup","panel","actions","open","cancel","height","openPopup","url","channel","width","height","left","top","windowSurface","openTab","opened","CLOSE_POLL_INTERVAL_MS","CLOSE_GRACE_MS","READY_TIMEOUT_MS","active","hasUserActivation","notify","callback","args","error","isPickerMessage","data","channel","message","MESSAGE_SOURCE","open","options","url","windowUrl","origin","validateOptions","workspaceOrigin","createChannel","pageOrigin","hostOrigin","buildOpenUrl","FreedamPickerError","mode","surface","frame","controls","opened","openTab","openPopup","openFrame","promise","resolve","reject","settled","readyNotified","closeTimer","readyTimer","closeSoon","checkClosed","closePoll","onMessage","openWindow","popup","fallBack","event","isServerErrorCode","RECOVERABLE_ERROR_CODES","VERSION"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codes the picker page sends in an `error` message.
|
|
3
|
+
*
|
|
4
|
+
* Keep in sync with `App\Enums\PickerErrorCode`: `tests/Unit/Enums/PickerErrorCodeTest.php`
|
|
5
|
+
* reads this list and fails when the two differ. `auth_required` belongs to
|
|
6
|
+
* the iframe modes and `guest_access_unavailable` to the access without an
|
|
7
|
+
* account (version 1.1); the picker never sends `auth_required` in a popup.
|
|
8
|
+
*/
|
|
9
|
+
declare const SERVER_ERROR_CODES: readonly ["origin_denied", "feature_unavailable", "integration_suspended", "integration_unavailable", "insert_failed", "auth_required", "guest_access_unavailable"];
|
|
10
|
+
/** Codes the SDK raises itself, without the picker page. */
|
|
11
|
+
declare const SDK_ERROR_CODES: readonly ["popup_blocked", "invalid_options"];
|
|
12
|
+
type PickerServerErrorCode = (typeof SERVER_ERROR_CODES)[number];
|
|
13
|
+
type PickerSdkErrorCode = (typeof SDK_ERROR_CODES)[number];
|
|
14
|
+
type PickerErrorCode = PickerServerErrorCode | PickerSdkErrorCode;
|
|
15
|
+
/**
|
|
16
|
+
* English messages for the developer console. The picker page shows its own
|
|
17
|
+
* translated text to the person using it.
|
|
18
|
+
*/
|
|
19
|
+
declare const ERROR_MESSAGES: Record<PickerErrorCode, string>;
|
|
20
|
+
/** Error passed to `onError` and used to reject the promise of `open()`. */
|
|
21
|
+
declare class FreedamPickerError extends Error {
|
|
22
|
+
readonly code: PickerErrorCode;
|
|
23
|
+
constructor(code: PickerErrorCode, detail?: string);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Types of one inserted asset, as the picker sends it in a `select` message.
|
|
28
|
+
*
|
|
29
|
+
* Written by hand from `schema/payload.schema.json`, which is the reference.
|
|
30
|
+
* `tests/payload-schema.test.ts` checks typed examples of each variant
|
|
31
|
+
* against the schema: a field added to one and not the other fails it.
|
|
32
|
+
* Keys are snake_case, like the Freedam API.
|
|
33
|
+
*/
|
|
34
|
+
/** Position from the top-left corner, in percent: usable as `object-position: x% y%`. */
|
|
35
|
+
interface PickerFocusPoint {
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
}
|
|
39
|
+
/** A named format of the publication channel, offered by the integration's profile. */
|
|
40
|
+
interface PickerFormat {
|
|
41
|
+
/** Name used in URL options (`fmt=hero`). */
|
|
42
|
+
name: string;
|
|
43
|
+
/** Width to height, e.g. `16:9`. */
|
|
44
|
+
ratio: string;
|
|
45
|
+
url: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Metadata fields the integration's profile exposes. A field the profile
|
|
49
|
+
* exposes is always present, `null` when empty or hidden from the person who
|
|
50
|
+
* inserted the asset; a field it does not expose is absent.
|
|
51
|
+
*/
|
|
52
|
+
interface PickerMetadata {
|
|
53
|
+
title?: string | null;
|
|
54
|
+
description?: string | null;
|
|
55
|
+
alt?: string | null;
|
|
56
|
+
credit?: string | null;
|
|
57
|
+
copyright?: string | null;
|
|
58
|
+
keywords?: string[] | null;
|
|
59
|
+
/** Workspace metadata fields, by field key. Dates are `YYYY-MM-DD`. */
|
|
60
|
+
custom?: Record<string, PickerMetadataValue>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A value as the Freedam API returns it: a list for several values, a map of
|
|
64
|
+
* language code to text for a multilingual field, `YYYY-MM-DD` for a date.
|
|
65
|
+
*/
|
|
66
|
+
type PickerMetadataValue = string | number | boolean | null | Array<string | number | boolean> | Record<string, string>;
|
|
67
|
+
/** Fields shared by every asset type. */
|
|
68
|
+
interface PickerAssetBase {
|
|
69
|
+
/** Global asset ID, usable with `GET /api/v1/assets/{gaid}`. */
|
|
70
|
+
gaid: string;
|
|
71
|
+
/** Asset version at insertion. URLs always serve the current version. */
|
|
72
|
+
version: number;
|
|
73
|
+
mime_type: string;
|
|
74
|
+
/** Size of the original file, in bytes. */
|
|
75
|
+
file_size: number;
|
|
76
|
+
width: number | null;
|
|
77
|
+
height: number | null;
|
|
78
|
+
/** Main URL for what the person chose in the picker. */
|
|
79
|
+
url: string;
|
|
80
|
+
/**
|
|
81
|
+
* Signed URL of the asset's image (the image itself, a video's poster,
|
|
82
|
+
* a document's first page) with the literal text `{options}` where the
|
|
83
|
+
* URL options go: replace it with `w=800`, `w=800,h=600`, `fmt=hero`…
|
|
84
|
+
*/
|
|
85
|
+
url_template: string;
|
|
86
|
+
/**
|
|
87
|
+
* Widths `url_template` serves with `w=`, in pixels, smallest first: the
|
|
88
|
+
* channel's width tiers up to the original's width (version 1.2). The
|
|
89
|
+
* smallest tier when the original is narrower than it; every tier when
|
|
90
|
+
* the original's width is unknown. A width above the channel maximum
|
|
91
|
+
* answers 400, so build `srcset` from this list.
|
|
92
|
+
*/
|
|
93
|
+
widths: number[];
|
|
94
|
+
formats: PickerFormat[];
|
|
95
|
+
focus_point: PickerFocusPoint | null;
|
|
96
|
+
/** Image at least 400 pixels wide. */
|
|
97
|
+
thumbnail_url: string;
|
|
98
|
+
metadata: PickerMetadata;
|
|
99
|
+
/** Usage recorded at insertion; `DELETE /api/v1/usages/{usage_id}` removes it. */
|
|
100
|
+
usage_id: string;
|
|
101
|
+
/** Authorizes the usage operations of this insertion, and nothing else. */
|
|
102
|
+
usage_token: string;
|
|
103
|
+
}
|
|
104
|
+
/** Image file format chosen in the picker; `null` lets the browser negotiate it. */
|
|
105
|
+
type PickerFileFormat = 'avif' | 'webp' | 'jpeg';
|
|
106
|
+
interface PickerImageChoice {
|
|
107
|
+
/** Format name, or `null` for the original ratio. */
|
|
108
|
+
format: string | null;
|
|
109
|
+
file_format: PickerFileFormat | null;
|
|
110
|
+
}
|
|
111
|
+
interface PickerImage extends PickerAssetBase {
|
|
112
|
+
type: 'image';
|
|
113
|
+
choice: PickerImageChoice;
|
|
114
|
+
}
|
|
115
|
+
interface PickerSubtitle {
|
|
116
|
+
language: string;
|
|
117
|
+
label: string;
|
|
118
|
+
url: string;
|
|
119
|
+
is_default: boolean;
|
|
120
|
+
}
|
|
121
|
+
interface PickerVideoChoice {
|
|
122
|
+
autoplay: boolean;
|
|
123
|
+
/** Always `true` when `autoplay` is `true`. */
|
|
124
|
+
muted: boolean;
|
|
125
|
+
loop: boolean;
|
|
126
|
+
controls: boolean;
|
|
127
|
+
/** Start position, in seconds. */
|
|
128
|
+
start: number;
|
|
129
|
+
/**
|
|
130
|
+
* Subtitles shown when playback starts: `null` for the video's default
|
|
131
|
+
* track, `'off'` for none, otherwise the language code of one of `subtitles`.
|
|
132
|
+
*/
|
|
133
|
+
subtitle_language: string | null;
|
|
134
|
+
}
|
|
135
|
+
interface PickerVideo extends PickerAssetBase {
|
|
136
|
+
type: 'video';
|
|
137
|
+
/** Equal to `player_url`. */
|
|
138
|
+
url: string;
|
|
139
|
+
player_url: string;
|
|
140
|
+
/** Responsive `<iframe>` of `player_url`. */
|
|
141
|
+
embed_html: string;
|
|
142
|
+
/** HLS manifest. */
|
|
143
|
+
stream_url: string;
|
|
144
|
+
poster_url: string;
|
|
145
|
+
/** In seconds; `null` when unknown. */
|
|
146
|
+
duration: number | null;
|
|
147
|
+
subtitles: PickerSubtitle[];
|
|
148
|
+
choice: PickerVideoChoice;
|
|
149
|
+
}
|
|
150
|
+
/** What the host inserts: a link to `url`, the `preview_url` image linked to `url`, or `embed_html`. */
|
|
151
|
+
type PickerDocumentForm = 'link' | 'thumbnail' | 'viewer';
|
|
152
|
+
/** Which file `url` delivers. */
|
|
153
|
+
type PickerDocumentFile = 'original' | 'pdf';
|
|
154
|
+
interface PickerDocumentChoice {
|
|
155
|
+
form: PickerDocumentForm;
|
|
156
|
+
file: PickerDocumentFile;
|
|
157
|
+
}
|
|
158
|
+
interface PickerDocument extends PickerAssetBase {
|
|
159
|
+
type: 'document';
|
|
160
|
+
/** `download_url` or `pdf_url`, as `choice.file` says. */
|
|
161
|
+
url: string;
|
|
162
|
+
download_url: string;
|
|
163
|
+
/** Equal to `download_url` for a PDF; `null` when no PDF version exists. */
|
|
164
|
+
pdf_url: string | null;
|
|
165
|
+
/** First page, served as an image; `null` when the document has no preview. */
|
|
166
|
+
preview_url: string | null;
|
|
167
|
+
/** Page-by-page viewer; `null` when no PDF version exists. */
|
|
168
|
+
viewer_url: string | null;
|
|
169
|
+
/** Responsive `<iframe>` of `viewer_url`; `null` when no PDF version exists. */
|
|
170
|
+
embed_html: string | null;
|
|
171
|
+
page_count: number | null;
|
|
172
|
+
extension: string;
|
|
173
|
+
choice: PickerDocumentChoice;
|
|
174
|
+
}
|
|
175
|
+
type PickerAsset = PickerImage | PickerVideo | PickerDocument;
|
|
176
|
+
type PickerAssetType = PickerAsset['type'];
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Protocol between the picker window (served on the workspace host) and the
|
|
180
|
+
* host page that opened it.
|
|
181
|
+
*
|
|
182
|
+
* This file is the only definition of the protocol: the SDK imports it, and
|
|
183
|
+
* the Freedam application imports it through the `@freedam/picker-protocol`
|
|
184
|
+
* alias (tsconfig.json and vite.config.ts) for the picker page's side.
|
|
185
|
+
*
|
|
186
|
+
* The protocol version is independent of the SDK version. A receiver ignores
|
|
187
|
+
* any message `type` it does not know, so adding a message is compatible.
|
|
188
|
+
*/
|
|
189
|
+
|
|
190
|
+
declare const PROTOCOL_VERSION = 1;
|
|
191
|
+
type PickerProtocolVersion = typeof PROTOCOL_VERSION;
|
|
192
|
+
/** Value of `source` in every message, to discard other scripts' messages quickly. */
|
|
193
|
+
declare const MESSAGE_SOURCE = "freedam-picker";
|
|
194
|
+
/** Most assets one insertion can return. */
|
|
195
|
+
declare const MAX_SELECTION = 50;
|
|
196
|
+
declare const PICKER_MODES: readonly ["popup", "modal", "inline", "tab"];
|
|
197
|
+
/**
|
|
198
|
+
* How the picker opens: in a popup window on the workspace host (`popup`),
|
|
199
|
+
* in an iframe over the host page (`modal`, version 1.1), in an iframe
|
|
200
|
+
* inside an element of the host page (`inline`, version 1.1) or in a new
|
|
201
|
+
* tab of the browser (`tab`, version 1.2).
|
|
202
|
+
*/
|
|
203
|
+
type PickerMode = (typeof PICKER_MODES)[number];
|
|
204
|
+
/** Modes that show the picker in a window of its own, which posts to `window.opener`. */
|
|
205
|
+
type PickerWindowMode = Extract<PickerMode, 'popup' | 'tab'>;
|
|
206
|
+
/** Modes that show the picker in an iframe of the host page. */
|
|
207
|
+
type PickerFrameMode = Exclude<PickerMode, PickerWindowMode>;
|
|
208
|
+
/** Smallest height of the inline iframe, in CSS pixels. */
|
|
209
|
+
declare const INLINE_MIN_HEIGHT = 480;
|
|
210
|
+
/** Values of the `types` option (version 1.2): the payload's asset types. */
|
|
211
|
+
declare const PICKER_ASSET_TYPES: readonly ["image", "video", "document"];
|
|
212
|
+
declare const SELECT_MODES: readonly ["single", "multiple"];
|
|
213
|
+
type PickerSelectMode = (typeof SELECT_MODES)[number];
|
|
214
|
+
/** Interface languages of Freedam. */
|
|
215
|
+
declare const PICKER_LOCALES: readonly ["en", "fr", "de", "es", "pt-BR", "zh-CN"];
|
|
216
|
+
type PickerLocale = (typeof PICKER_LOCALES)[number];
|
|
217
|
+
declare const PICKER_THEMES: readonly ["light", "dark", "auto"];
|
|
218
|
+
type PickerTheme = (typeof PICKER_THEMES)[number];
|
|
219
|
+
/** Sort fields, as for `sort_by` in `GET /api/v1/assets`. */
|
|
220
|
+
declare const SORT_FIELDS: readonly ["created_at", "updated_at", "title", "original_filename", "size", "relevance"];
|
|
221
|
+
type SortField = (typeof SORT_FIELDS)[number];
|
|
222
|
+
declare const SORT_DIRECTIONS: readonly ["asc", "desc"];
|
|
223
|
+
type SortDirection = (typeof SORT_DIRECTIONS)[number];
|
|
224
|
+
/**
|
|
225
|
+
* One condition of a filter tree, as for `filters` in `GET /api/v1/assets`,
|
|
226
|
+
* e.g. `{ type: 'filter', field: 'asset.file_category', operator: 'equals', value: 'Image' }`.
|
|
227
|
+
* The server validates fields, operators and values.
|
|
228
|
+
*/
|
|
229
|
+
interface RuleFilter {
|
|
230
|
+
type: 'filter';
|
|
231
|
+
field: string;
|
|
232
|
+
operator: string;
|
|
233
|
+
value?: unknown;
|
|
234
|
+
}
|
|
235
|
+
interface RuleGroup {
|
|
236
|
+
type: 'group';
|
|
237
|
+
operator: 'and' | 'or';
|
|
238
|
+
rules: RuleTree[];
|
|
239
|
+
}
|
|
240
|
+
type RuleTree = RuleFilter | RuleGroup;
|
|
241
|
+
/**
|
|
242
|
+
* Query parameters of the opening URL `https://{workspace}/picker/{integration}`.
|
|
243
|
+
* None of them is a secret. The picker page keeps them for the whole session
|
|
244
|
+
* of its window, including the reload after the person signs in.
|
|
245
|
+
*/
|
|
246
|
+
interface PickerOpenQuery {
|
|
247
|
+
protocol: `${PickerProtocolVersion}`;
|
|
248
|
+
/** Origin of the host page, checked by the server against the integration's origins. */
|
|
249
|
+
origin: string;
|
|
250
|
+
/** Random nonce, 22 base64url characters, repeated in every message. */
|
|
251
|
+
channel: string;
|
|
252
|
+
/** The server also reads it to start the iframe's own session (`modal`, `inline`). */
|
|
253
|
+
mode: PickerMode;
|
|
254
|
+
select: PickerSelectMode;
|
|
255
|
+
/** 1 to MAX_SELECTION, only with `select=multiple`. */
|
|
256
|
+
max?: string;
|
|
257
|
+
q?: string;
|
|
258
|
+
/** RuleTree as JSON. */
|
|
259
|
+
filters?: string;
|
|
260
|
+
sort_by?: SortField;
|
|
261
|
+
sort_direction?: SortDirection;
|
|
262
|
+
/** Comma-separated GAIDs; the picker ignores those the person cannot see. */
|
|
263
|
+
preselected?: string;
|
|
264
|
+
/**
|
|
265
|
+
* Comma-separated asset types, version 1.2. The picker offers only these
|
|
266
|
+
* among the integration's own types; a type the integration does not
|
|
267
|
+
* offer is ignored.
|
|
268
|
+
*/
|
|
269
|
+
types?: string;
|
|
270
|
+
/** Key of the integration's access without an account (`fdpk_…`), version 1.1. */
|
|
271
|
+
public_key?: string;
|
|
272
|
+
locale?: PickerLocale;
|
|
273
|
+
theme?: PickerTheme;
|
|
274
|
+
}
|
|
275
|
+
/** Fields of every message. */
|
|
276
|
+
interface PickerMessageBase {
|
|
277
|
+
source: typeof MESSAGE_SOURCE;
|
|
278
|
+
protocol: PickerProtocolVersion;
|
|
279
|
+
channel: string;
|
|
280
|
+
}
|
|
281
|
+
/** The picker page is interactive. Sent on every page load, including after sign-in. */
|
|
282
|
+
interface PickerReadyMessage extends PickerMessageBase {
|
|
283
|
+
type: 'ready';
|
|
284
|
+
integration: string;
|
|
285
|
+
}
|
|
286
|
+
/** The insertion succeeded; the picker window closes next. */
|
|
287
|
+
interface PickerSelectMessage extends PickerMessageBase {
|
|
288
|
+
type: 'select';
|
|
289
|
+
assets: PickerAsset[];
|
|
290
|
+
}
|
|
291
|
+
/** The person pressed Cancel; the picker window closes next. */
|
|
292
|
+
interface PickerCancelMessage extends PickerMessageBase {
|
|
293
|
+
type: 'cancel';
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* The picker in an iframe shows its sign-in screen (version 1.1): the page
|
|
297
|
+
* loaded and waits for the person, and `ready` follows once they have
|
|
298
|
+
* signed in or continued without an account.
|
|
299
|
+
*/
|
|
300
|
+
interface PickerSignInMessage extends PickerMessageBase {
|
|
301
|
+
type: 'sign_in';
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Height the picker's content needs, in CSS pixels (version 1.1). The SDK
|
|
305
|
+
* applies it to the inline iframe, never below INLINE_MIN_HEIGHT, and
|
|
306
|
+
* ignores it in the other modes.
|
|
307
|
+
*/
|
|
308
|
+
interface PickerResizeMessage extends PickerMessageBase {
|
|
309
|
+
type: 'resize';
|
|
310
|
+
height: number;
|
|
311
|
+
}
|
|
312
|
+
/** An error the picker page also shows to the person; the window stays open. */
|
|
313
|
+
interface PickerErrorMessage extends PickerMessageBase {
|
|
314
|
+
type: 'error';
|
|
315
|
+
code: PickerServerErrorCode;
|
|
316
|
+
}
|
|
317
|
+
type PickerToHostMessage = PickerReadyMessage | PickerSelectMessage | PickerCancelMessage | PickerErrorMessage | PickerSignInMessage | PickerResizeMessage;
|
|
318
|
+
/**
|
|
319
|
+
* The host page removes the picker's iframe (version 1.1): sent by the SDK
|
|
320
|
+
* to the iframe, with the workspace origin as `targetOrigin`, just before it
|
|
321
|
+
* removes it after an insertion or a cancellation, or when the person
|
|
322
|
+
* closes the modal. The picker page does not answer it.
|
|
323
|
+
*/
|
|
324
|
+
interface HostCloseMessage extends PickerMessageBase {
|
|
325
|
+
type: 'close';
|
|
326
|
+
}
|
|
327
|
+
/** Messages of the host page to the picker; only the iframe modes send one. */
|
|
328
|
+
type HostToPickerMessage = HostCloseMessage;
|
|
329
|
+
|
|
330
|
+
interface PickerOptions {
|
|
331
|
+
/** Host of the Freedam workspace, without scheme or path: `acme.example.com`. HTTPS is always used. */
|
|
332
|
+
workspace: string;
|
|
333
|
+
/** Public key of the integration, shown in the workspace's integration settings. */
|
|
334
|
+
integration: string;
|
|
335
|
+
/**
|
|
336
|
+
* Key of the integration's access without a Freedam account (`fdpk_…`),
|
|
337
|
+
* shown in the integration's settings when the access is on. With it,
|
|
338
|
+
* visitors without an account can browse and insert the integration's
|
|
339
|
+
* assets. Not a secret.
|
|
340
|
+
*/
|
|
341
|
+
publicKey?: string;
|
|
342
|
+
/** Default `single`. */
|
|
343
|
+
select?: PickerSelectMode;
|
|
344
|
+
/** Most assets the person can choose, 1 to 50, with `select: 'multiple'`. */
|
|
345
|
+
max?: number;
|
|
346
|
+
/** Initial search text, 500 characters at most. */
|
|
347
|
+
q?: string;
|
|
348
|
+
/** Initial filters, applied on top of the integration's own rules. */
|
|
349
|
+
filters?: RuleTree;
|
|
350
|
+
sort?: {
|
|
351
|
+
by: SortField;
|
|
352
|
+
direction?: SortDirection;
|
|
353
|
+
};
|
|
354
|
+
/** GAIDs selected when the picker opens; GAIDs the person cannot see are ignored. */
|
|
355
|
+
preselected?: string[];
|
|
356
|
+
/**
|
|
357
|
+
* Asset types the picker offers, among the integration's own: `['image']`
|
|
358
|
+
* for an image field. A type the integration does not offer is ignored.
|
|
359
|
+
* Default: every type of the integration.
|
|
360
|
+
*/
|
|
361
|
+
types?: PickerAssetType[];
|
|
362
|
+
/** Interface language; default: the person's own setting. */
|
|
363
|
+
locale?: PickerLocale;
|
|
364
|
+
theme?: PickerTheme;
|
|
365
|
+
/**
|
|
366
|
+
* Default `popup`: a window on the workspace host. `tab` opens it in a
|
|
367
|
+
* new tab of the browser instead. `modal` shows the picker in an iframe
|
|
368
|
+
* over the page (full screen below 640 pixels wide); `inline` shows it
|
|
369
|
+
* in an iframe inside `container`. When the browser does not let the
|
|
370
|
+
* iframe sign in, the SDK offers to continue in a window.
|
|
371
|
+
*/
|
|
372
|
+
mode?: PickerMode;
|
|
373
|
+
/** Element that receives the picker's iframe, required with `mode: 'inline'` and only then. */
|
|
374
|
+
container?: HTMLElement;
|
|
375
|
+
onReady?: () => void;
|
|
376
|
+
onSelect?: (assets: PickerAsset[]) => void;
|
|
377
|
+
onCancel?: () => void;
|
|
378
|
+
onError?: (error: FreedamPickerError) => void;
|
|
379
|
+
onClose?: () => void;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
type PickerResult = PickerAsset[] | null;
|
|
383
|
+
/**
|
|
384
|
+
* Opens the picker and resolves with the inserted assets, or with `null`
|
|
385
|
+
* when the person cancels or closes it.
|
|
386
|
+
*
|
|
387
|
+
* Call it from a click handler, with no `await` before it: browsers block
|
|
388
|
+
* windows opened outside a user gesture. Invalid options reject with
|
|
389
|
+
* `invalid_options` before anything opens. While a picker of this page is
|
|
390
|
+
* open, another call brings it to the front and returns the same promise.
|
|
391
|
+
*
|
|
392
|
+
* In the `popup` and `tab` modes the picker runs in a window of its own,
|
|
393
|
+
* a popup or a new tab of the browser.
|
|
394
|
+
*
|
|
395
|
+
* In the `modal` and `inline` modes the picker runs in an iframe. When the
|
|
396
|
+
* iframe cannot sign in (`auth_required`) or the picker page sends nothing
|
|
397
|
+
* within READY_TIMEOUT_MS, the iframe gives way to an "Open Freedam"
|
|
398
|
+
* button that opens the picker in a window with the same options; the
|
|
399
|
+
* promise then settles with the outcome of that window.
|
|
400
|
+
*/
|
|
401
|
+
declare function open(options: PickerOptions): Promise<PickerResult>;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* `<freedam-picker>`: a click inside the element opens the picker with the
|
|
405
|
+
* element's attributes as options. No shadow DOM, no style, no text: the
|
|
406
|
+
* host page puts its own button inside.
|
|
407
|
+
*
|
|
408
|
+
* ```html
|
|
409
|
+
* <freedam-picker workspace="acme.example.com" integration="int_8f3k2" select="multiple" max="5">
|
|
410
|
+
* <button type="button">Insert from Freedam</button>
|
|
411
|
+
* </freedam-picker>
|
|
412
|
+
* ```
|
|
413
|
+
*
|
|
414
|
+
* `mode="modal"` shows the picker over the page; `mode="inline"` shows it in
|
|
415
|
+
* the element whose `id` is in the `container` attribute (or in the
|
|
416
|
+
* `container` property).
|
|
417
|
+
*
|
|
418
|
+
* Importing this module defines the element (`import '@freedam/picker/element'`);
|
|
419
|
+
* the script-tag build defines it too. A second definition is skipped, so the
|
|
420
|
+
* module can load twice.
|
|
421
|
+
*/
|
|
422
|
+
|
|
423
|
+
/** Events the element dispatches; they bubble and cross shadow roots. */
|
|
424
|
+
interface FreedamPickerEventMap {
|
|
425
|
+
'freedam-ready': CustomEvent<Record<string, never>>;
|
|
426
|
+
'freedam-select': CustomEvent<{
|
|
427
|
+
assets: PickerAsset[];
|
|
428
|
+
}>;
|
|
429
|
+
'freedam-cancel': CustomEvent<Record<string, never>>;
|
|
430
|
+
'freedam-error': CustomEvent<{
|
|
431
|
+
error: FreedamPickerError;
|
|
432
|
+
}>;
|
|
433
|
+
'freedam-close': CustomEvent<Record<string, never>>;
|
|
434
|
+
}
|
|
435
|
+
declare const BaseElement: typeof HTMLElement;
|
|
436
|
+
declare class FreedamPickerElement extends BaseElement {
|
|
437
|
+
private filtersValue;
|
|
438
|
+
private preselectedValue;
|
|
439
|
+
private containerValue;
|
|
440
|
+
constructor();
|
|
441
|
+
/** Initial filters; set this property instead of the `filters` attribute to pass an object. */
|
|
442
|
+
get filters(): RuleTree | undefined;
|
|
443
|
+
set filters(value: RuleTree | undefined);
|
|
444
|
+
/** GAIDs selected when the picker opens; set this property instead of the `preselected` attribute to pass an array. */
|
|
445
|
+
get preselected(): string[] | undefined;
|
|
446
|
+
set preselected(value: string[] | undefined);
|
|
447
|
+
/**
|
|
448
|
+
* Element that receives the picker with `mode="inline"`; set this property
|
|
449
|
+
* or the `container` attribute, which holds the element's `id`.
|
|
450
|
+
*/
|
|
451
|
+
get container(): HTMLElement | undefined;
|
|
452
|
+
set container(value: HTMLElement | undefined);
|
|
453
|
+
/**
|
|
454
|
+
* Opens the picker with the element's attributes, as a click inside it
|
|
455
|
+
* does. Call it from a click handler: browsers block windows opened
|
|
456
|
+
* outside a user gesture.
|
|
457
|
+
*/
|
|
458
|
+
open(): Promise<PickerAsset[] | null>;
|
|
459
|
+
private options;
|
|
460
|
+
private emit;
|
|
461
|
+
}
|
|
462
|
+
declare global {
|
|
463
|
+
interface HTMLElementTagNameMap {
|
|
464
|
+
'freedam-picker': FreedamPickerElement;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/** Version of this SDK, equal to `version` in package.json (checked by tests/version.test.ts). */
|
|
469
|
+
declare const VERSION = "1.2.0";
|
|
470
|
+
|
|
471
|
+
export { ERROR_MESSAGES, FreedamPickerElement, FreedamPickerError, type FreedamPickerEventMap, type HostCloseMessage, type HostToPickerMessage, INLINE_MIN_HEIGHT, MAX_SELECTION, PICKER_ASSET_TYPES, PICKER_LOCALES, PICKER_MODES, PICKER_THEMES, PROTOCOL_VERSION, type PickerAsset, type PickerAssetType, type PickerCancelMessage, type PickerDocument, type PickerDocumentChoice, type PickerDocumentFile, type PickerDocumentForm, type PickerErrorCode, type PickerErrorMessage, type PickerFileFormat, type PickerFocusPoint, type PickerFormat, type PickerFrameMode, type PickerImage, type PickerImageChoice, type PickerLocale, type PickerMessageBase, type PickerMetadata, type PickerMetadataValue, type PickerMode, type PickerOpenQuery, type PickerOptions, type PickerReadyMessage, type PickerResizeMessage, type PickerSdkErrorCode, type PickerSelectMessage, type PickerSelectMode, type PickerServerErrorCode, type PickerSignInMessage, type PickerSubtitle, type PickerTheme, type PickerToHostMessage, type PickerVideo, type PickerVideoChoice, type RuleFilter, type RuleGroup, type RuleTree, SDK_ERROR_CODES, SERVER_ERROR_CODES, SORT_DIRECTIONS, SORT_FIELDS, type SortDirection, type SortField, VERSION, open };
|