@webiny/website-builder-sdk 6.4.12-beta.5 → 6.5.0-beta.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.
Files changed (79) hide show
  1. package/BindingsResolver.d.ts +4 -1
  2. package/BindingsResolver.js.map +1 -1
  3. package/ComponentResolver.d.ts +3 -2
  4. package/ComponentResolver.js +6 -3
  5. package/ComponentResolver.js.map +1 -1
  6. package/ContentSdk.d.ts +2 -0
  7. package/ContentSdk.js +5 -0
  8. package/ContentSdk.js.map +1 -1
  9. package/asset/deliveryUrl.d.ts +41 -0
  10. package/asset/deliveryUrl.js +34 -0
  11. package/asset/deliveryUrl.js.map +1 -0
  12. package/asset/deliveryUrl.test.js +127 -0
  13. package/asset/deliveryUrl.test.js.map +1 -0
  14. package/asset/geometry.d.ts +7 -0
  15. package/asset/geometry.js +10 -0
  16. package/asset/geometry.js.map +1 -0
  17. package/asset/imageSrcSet.d.ts +63 -0
  18. package/asset/imageSrcSet.js +98 -0
  19. package/asset/imageSrcSet.js.map +1 -0
  20. package/asset/imageSrcSet.test.d.ts +1 -0
  21. package/asset/imageSrcSet.test.js +103 -0
  22. package/asset/imageSrcSet.test.js.map +1 -0
  23. package/asset/index.d.ts +5 -0
  24. package/asset/index.js +5 -0
  25. package/asset/normalize.d.ts +24 -0
  26. package/asset/normalize.js +102 -0
  27. package/asset/normalize.js.map +1 -0
  28. package/asset/normalize.test.d.ts +1 -0
  29. package/asset/normalize.test.js +256 -0
  30. package/asset/normalize.test.js.map +1 -0
  31. package/asset/types.d.ts +2 -0
  32. package/asset/types.js +0 -0
  33. package/contentEntry/ContentEntryBindingsResolver.d.ts +30 -0
  34. package/contentEntry/ContentEntryBindingsResolver.js +45 -0
  35. package/contentEntry/ContentEntryBindingsResolver.js.map +1 -0
  36. package/contentEntry/ContentEntryCache.d.ts +46 -0
  37. package/contentEntry/ContentEntryCache.js +50 -0
  38. package/contentEntry/ContentEntryCache.js.map +1 -0
  39. package/contentEntry/resolveContentEntry.d.ts +74 -0
  40. package/contentEntry/resolveContentEntry.js +53 -0
  41. package/contentEntry/resolveContentEntry.js.map +1 -0
  42. package/contentEntry/resolveContentEntry.test.d.ts +1 -0
  43. package/contentEntry/resolveContentEntry.test.js +213 -0
  44. package/contentEntry/resolveContentEntry.test.js.map +1 -0
  45. package/createComponent.d.ts +5 -0
  46. package/createComponent.js +32 -0
  47. package/createComponent.js.map +1 -0
  48. package/createInput.d.ts +10 -2
  49. package/createInput.js +8 -1
  50. package/createInput.js.map +1 -1
  51. package/dataProviders/ApiClient.d.ts +3 -1
  52. package/dataProviders/ApiClient.js +4 -2
  53. package/dataProviders/ApiClient.js.map +1 -1
  54. package/dataProviders/GET_PAGE_BY_ID.d.ts +1 -1
  55. package/dataProviders/GET_PAGE_BY_ID.js +0 -2
  56. package/dataProviders/GET_PAGE_BY_ID.js.map +1 -1
  57. package/dataProviders/GET_PAGE_BY_PATH.d.ts +1 -1
  58. package/dataProviders/GET_PAGE_BY_PATH.js +0 -2
  59. package/dataProviders/GET_PAGE_BY_PATH.js.map +1 -1
  60. package/image/geometry.d.ts +54 -0
  61. package/image/geometry.js +112 -0
  62. package/image/geometry.js.map +1 -0
  63. package/image/geometry.test.d.ts +1 -0
  64. package/image/geometry.test.js +190 -0
  65. package/image/geometry.test.js.map +1 -0
  66. package/image/index.d.ts +3 -0
  67. package/image/index.js +3 -0
  68. package/image/presets.d.ts +6 -0
  69. package/image/presets.js +20 -0
  70. package/image/presets.js.map +1 -0
  71. package/image/types.d.ts +10 -0
  72. package/image/types.js +0 -0
  73. package/index.d.ts +6 -0
  74. package/index.js +6 -0
  75. package/package.json +7 -6
  76. package/types.d.ts +69 -1
  77. package/dataProviders/queries.test.js +0 -45
  78. package/dataProviders/queries.test.js.map +0 -1
  79. /package/{dataProviders/queries.test.d.ts → asset/deliveryUrl.test.d.ts} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"createInput.js","sources":["../src/createInput.ts"],"sourcesContent":["import type {\n TextInput,\n LongTextInput,\n NumberInput,\n BooleanInput,\n ColorInput,\n FileInput,\n LexicalInput,\n SelectInput,\n RadioInput,\n ObjectInput,\n DateTimeInput,\n ComponentInput,\n TagsInput,\n SlotInput\n} from \"./types.js\";\nimport { functionConverter } from \"~/FunctionConverter.js\";\n\nexport type InputFactory<Name extends string> =\n | ReturnType<typeof createTextInput<Name>>\n | ReturnType<typeof createLongTextInput<Name>>\n | ReturnType<typeof createNumberInput<Name>>\n | ReturnType<typeof createBooleanInput<Name>>\n | ReturnType<typeof createColorInput<Name>>\n | ReturnType<typeof createFileInput<Name>>\n | ReturnType<typeof createDateInput<Name>>\n | ReturnType<typeof createLexicalInput<Name>>\n | ReturnType<typeof createSelectInput<Name>>\n | ReturnType<typeof createRadioInput<Name>>\n | ReturnType<typeof createObjectInput<Name>>\n | ReturnType<typeof createTagsInput<Name>>\n | ReturnType<typeof createSlotInput<Name>>;\n\n/**\n * TypeScript Overload Resolution and Input Factory Design\n *\n * Each input factory (e.g., createTextInput) is defined with two TypeScript overload signatures:\n * 1. One overload requires a `name` property in the input (used for array-based APIs, where inference is needed).\n * 2. The other overload omits the `name` property (used for object-based APIs, where the key provides the name).\n *\n * TypeScript resolves overloads by scanning from top to bottom and picking the first matching signature.\n * The implementation signature uses `any` for the argument, and the return type is unified to match all overloads.\n *\n * In array mode, the `name` must be explicitly provided in the input object, so inference for `TName` works.\n * In object mode, the input does not require a `name` property; instead, the key in the object (e.g., `{ foo: createTextInput({ ... }) }`)\n * provides the name via contextual typing from the consuming function (such as `createComponent`). TypeScript uses this context\n * to resolve the correct type for `TName` even when `name` is omitted from the input.\n *\n * This pattern ensures that all returned input objects are typed as `{ name: TName }`, whether `name` is provided explicitly or inferred,\n * so consumers of these factories do not need to worry about the presence of the `name` property in the result.\n */\n// Text\nexport function createTextInput<TName extends string>(\n input: { name: TName } & Omit<TextInput, \"type\" | \"name\">\n): TextInput & { name: TName };\nexport function createTextInput<TName extends string>(\n input: Omit<TextInput, \"type\" | \"name\">\n): TextInput & { name: TName };\nexport function createTextInput<TName extends string>(input: any): TextInput & { name: TName } {\n return createInput({\n type: \"text\",\n renderer: \"Webiny/Input\",\n ...input\n }) as TextInput & { name: TName };\n}\n\n// Long Text\nexport function createLongTextInput<TName extends string>(\n input: { name: TName } & Omit<LongTextInput, \"type\" | \"name\">\n): LongTextInput & { name: TName };\nexport function createLongTextInput<TName extends string>(\n input: Omit<LongTextInput, \"type\" | \"name\">\n): LongTextInput & { name: TName };\nexport function createLongTextInput<TName extends string>(\n input: any\n): LongTextInput & { name: TName } {\n return createInput({\n type: \"longText\",\n renderer: \"Webiny/Textarea\",\n ...input\n }) as LongTextInput & { name: TName };\n}\n\n// Number\nexport function createNumberInput<TName extends string>(\n input: { name: TName } & Omit<NumberInput, \"type\" | \"name\">\n): NumberInput & { name: TName };\nexport function createNumberInput<TName extends string>(\n input: Omit<NumberInput, \"type\" | \"name\">\n): NumberInput & { name: TName };\nexport function createNumberInput<TName extends string>(input: any): NumberInput & { name: TName } {\n return createInput({\n type: \"number\",\n renderer: \"Webiny/Number\",\n ...input\n }) as NumberInput & { name: TName };\n}\n\n// Boolean\nexport function createBooleanInput<TName extends string>(\n input: { name: TName } & Omit<BooleanInput, \"type\" | \"name\">\n): BooleanInput & { name: TName };\nexport function createBooleanInput<TName extends string>(\n input: Omit<BooleanInput, \"type\" | \"name\">\n): BooleanInput & { name: TName };\nexport function createBooleanInput<TName extends string>(\n input: any\n): BooleanInput & { name: TName } {\n return createInput({\n type: \"boolean\",\n renderer: \"Webiny/Switch\",\n ...input\n }) as BooleanInput & { name: TName };\n}\n\n// Color\nexport function createColorInput<TName extends string>(\n input: { name: TName } & Omit<ColorInput, \"type\" | \"name\">\n): ColorInput & { name: TName };\nexport function createColorInput<TName extends string>(\n input: Omit<ColorInput, \"type\" | \"name\">\n): ColorInput & { name: TName };\nexport function createColorInput<TName extends string>(input: any): ColorInput & { name: TName } {\n return createInput({\n type: \"color\",\n renderer: \"Webiny/ColorPicker\",\n ...input\n }) as ColorInput & { name: TName };\n}\n\n// File\nexport function createFileInput<TName extends string>(\n input: { name: TName } & Omit<FileInput, \"type\" | \"name\">\n): FileInput & { name: TName };\nexport function createFileInput<TName extends string>(\n input: Omit<FileInput, \"type\" | \"name\">\n): FileInput & { name: TName };\nexport function createFileInput<TName extends string>(input: any): FileInput & { name: TName } {\n return createInput({\n type: \"file\",\n renderer: \"Webiny/FileManager\",\n ...input\n }) as FileInput & { name: TName };\n}\n\n// Date\nexport function createDateInput<TName extends string>(\n input: { name: TName } & Omit<DateTimeInput, \"type\" | \"name\">\n): DateTimeInput & { name: TName };\nexport function createDateInput<TName extends string>(\n input: Omit<DateTimeInput, \"type\" | \"name\">\n): DateTimeInput & { name: TName };\nexport function createDateInput<TName extends string>(input: any): DateTimeInput & { name: TName } {\n return createInput({\n type: \"datetime\",\n renderer: \"Webiny/DateTime\",\n ...input\n }) as DateTimeInput & { name: TName };\n}\n\n// Rich Text\nexport function createLexicalInput<TName extends string>(\n input: { name: TName } & Omit<LexicalInput, \"type\" | \"name\">\n): LexicalInput & { name: TName };\nexport function createLexicalInput<TName extends string>(\n input: Omit<LexicalInput, \"type\" | \"name\">\n): LexicalInput & { name: TName };\nexport function createLexicalInput<TName extends string>(\n input: any\n): LexicalInput & { name: TName } {\n return createInput({\n type: \"lexical\",\n renderer: \"Webiny/Lexical\",\n ...input\n }) as LexicalInput & { name: TName };\n}\n\n// Select\nexport function createSelectInput<TName extends string>(\n input: { name: TName } & Omit<SelectInput, \"type\" | \"name\">\n): SelectInput & { name: TName };\nexport function createSelectInput<TName extends string>(\n input: Omit<SelectInput, \"type\" | \"name\">\n): SelectInput & { name: TName };\nexport function createSelectInput<TName extends string>(input: any): SelectInput & { name: TName } {\n return createInput({\n type: \"select\",\n renderer: \"Webiny/Select\",\n ...input\n }) as SelectInput & { name: TName };\n}\n\n// Radio\nexport function createRadioInput<TName extends string>(\n input: { name: TName } & Omit<RadioInput, \"type\" | \"name\">\n): RadioInput & { name: TName };\nexport function createRadioInput<TName extends string>(\n input: Omit<RadioInput, \"type\" | \"name\">\n): RadioInput & { name: TName };\nexport function createRadioInput<TName extends string>(input: any): RadioInput & { name: TName } {\n return createInput({\n type: \"radio\",\n renderer: \"Webiny/RadioGroup\",\n ...input\n }) as RadioInput & { name: TName };\n}\n\n// Object\nexport function createObjectInput<TName extends string>(\n input: { name: TName } & Omit<ObjectInput, \"type\" | \"name\">\n): ObjectInput & { name: TName };\nexport function createObjectInput<TName extends string>(\n input: Omit<ObjectInput, \"type\" | \"name\">\n): ObjectInput & { name: TName };\nexport function createObjectInput<TName extends string>(input: any): ObjectInput & { name: TName } {\n return createInput({\n type: \"object\",\n renderer: \"Webiny/Object\",\n ...input\n }) as ObjectInput & { name: TName };\n}\n\n// Tags\nexport function createTagsInput<TName extends string>(\n input: { name: TName } & Omit<TagsInput, \"type\" | \"name\">\n): TagsInput & { name: TName };\nexport function createTagsInput<TName extends string>(\n input: Omit<TagsInput, \"type\" | \"name\">\n): TagsInput & { name: TName };\nexport function createTagsInput<TName extends string>(input: any): TagsInput & { name: TName } {\n return createInput({\n type: \"text\",\n list: true,\n renderer: \"Webiny/Tags\",\n ...input\n }) as TagsInput & { name: TName };\n}\n\nexport function createSlotInput<TName extends string>(\n input: { name: TName } & Omit<SlotInput, \"type\" | \"name\">\n): SlotInput & { name: TName };\nexport function createSlotInput<TName extends string>(\n input: Omit<SlotInput, \"type\" | \"name\">\n): SlotInput & { name: TName };\nexport function createSlotInput<TName extends string>(input: any): SlotInput & { name: TName } {\n return createInput({\n type: \"slot\",\n list: true,\n renderer: \"Webiny/Slot\",\n defaultValue: [],\n ...input\n }) as SlotInput & { name: TName };\n}\n\n// Implementation\nexport function createInput(input: ComponentInput): ComponentInput {\n if (input.onChange) {\n // @ts-expect-error We don't use this function on the frontend, so this is ok.\n input.onChange = functionConverter.serialize(input.onChange!);\n }\n return input;\n}\n"],"names":["createTextInput","input","createInput","createLongTextInput","createNumberInput","createBooleanInput","createColorInput","createFileInput","createDateInput","createLexicalInput","createSelectInput","createRadioInput","createObjectInput","createTagsInput","createSlotInput","functionConverter"],"mappings":";AA0DO,SAASA,gBAAsCC,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASE,oBACZF,KAAU;IAEV,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASG,kBAAwCH,KAAU;IAC9D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASI,mBACZJ,KAAU;IAEV,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASK,iBAAuCL,KAAU;IAC7D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASM,gBAAsCN,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASO,gBAAsCP,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASQ,mBACZR,KAAU;IAEV,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASS,kBAAwCT,KAAU;IAC9D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASU,iBAAuCV,KAAU;IAC7D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASW,kBAAwCX,KAAU;IAC9D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASY,gBAAsCZ,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AAQO,SAASa,gBAAsCb,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,MAAM;QACN,UAAU;QACV,cAAc,EAAE;QAChB,GAAGD,KAAK;IACZ;AACJ;AAGO,SAASC,YAAYD,KAAqB;IAC7C,IAAIA,MAAM,QAAQ,EAEdA,MAAM,QAAQ,GAAGc,kBAAkB,SAAS,CAACd,MAAM,QAAQ;IAE/D,OAAOA;AACX"}
1
+ {"version":3,"file":"createInput.js","sources":["../src/createInput.ts"],"sourcesContent":["import type {\n TextInput,\n LongTextInput,\n NumberInput,\n BooleanInput,\n ColorInput,\n FileInput,\n LexicalInput,\n SelectInput,\n RadioInput,\n ObjectInput,\n DateTimeInput,\n ComponentInput,\n TagsInput,\n SlotInput,\n ContentEntryInput\n} from \"./types.js\";\nimport { functionConverter } from \"~/FunctionConverter.js\";\n\nexport type InputFactory<Name extends string> =\n | ReturnType<typeof createTextInput<Name>>\n | ReturnType<typeof createLongTextInput<Name>>\n | ReturnType<typeof createNumberInput<Name>>\n | ReturnType<typeof createBooleanInput<Name>>\n | ReturnType<typeof createColorInput<Name>>\n | ReturnType<typeof createFileInput<Name>>\n | ReturnType<typeof createDateInput<Name>>\n | ReturnType<typeof createLexicalInput<Name>>\n | ReturnType<typeof createSelectInput<Name>>\n | ReturnType<typeof createRadioInput<Name>>\n | ReturnType<typeof createObjectInput<Name>>\n | ReturnType<typeof createTagsInput<Name>>\n | ReturnType<typeof createSlotInput<Name>>\n | ReturnType<typeof createContentEntryInput<Name>>;\n\n/**\n * TypeScript Overload Resolution and Input Factory Design\n *\n * Each input factory (e.g., createTextInput) is defined with two TypeScript overload signatures:\n * 1. One overload requires a `name` property in the input (used for array-based APIs, where inference is needed).\n * 2. The other overload omits the `name` property (used for object-based APIs, where the key provides the name).\n *\n * TypeScript resolves overloads by scanning from top to bottom and picking the first matching signature.\n * The implementation signature uses `any` for the argument, and the return type is unified to match all overloads.\n *\n * In array mode, the `name` must be explicitly provided in the input object, so inference for `TName` works.\n * In object mode, the input does not require a `name` property; instead, the key in the object (e.g., `{ foo: createTextInput({ ... }) }`)\n * provides the name via contextual typing from the consuming function (such as `createComponent`). TypeScript uses this context\n * to resolve the correct type for `TName` even when `name` is omitted from the input.\n *\n * This pattern ensures that all returned input objects are typed as `{ name: TName }`, whether `name` is provided explicitly or inferred,\n * so consumers of these factories do not need to worry about the presence of the `name` property in the result.\n */\n// Text\nexport function createTextInput<TName extends string>(\n input: { name: TName } & Omit<TextInput, \"type\" | \"name\">\n): TextInput & { name: TName };\nexport function createTextInput<TName extends string>(\n input: Omit<TextInput, \"type\" | \"name\">\n): TextInput & { name: TName };\nexport function createTextInput<TName extends string>(input: any): TextInput & { name: TName } {\n return createInput({\n type: \"text\",\n renderer: \"Webiny/Input\",\n ...input\n }) as TextInput & { name: TName };\n}\n\n// Long Text\nexport function createLongTextInput<TName extends string>(\n input: { name: TName } & Omit<LongTextInput, \"type\" | \"name\">\n): LongTextInput & { name: TName };\nexport function createLongTextInput<TName extends string>(\n input: Omit<LongTextInput, \"type\" | \"name\">\n): LongTextInput & { name: TName };\nexport function createLongTextInput<TName extends string>(\n input: any\n): LongTextInput & { name: TName } {\n return createInput({\n type: \"longText\",\n renderer: \"Webiny/Textarea\",\n ...input\n }) as LongTextInput & { name: TName };\n}\n\n// Number\nexport function createNumberInput<TName extends string>(\n input: { name: TName } & Omit<NumberInput, \"type\" | \"name\">\n): NumberInput & { name: TName };\nexport function createNumberInput<TName extends string>(\n input: Omit<NumberInput, \"type\" | \"name\">\n): NumberInput & { name: TName };\nexport function createNumberInput<TName extends string>(input: any): NumberInput & { name: TName } {\n return createInput({\n type: \"number\",\n renderer: \"Webiny/Number\",\n ...input\n }) as NumberInput & { name: TName };\n}\n\n// Boolean\nexport function createBooleanInput<TName extends string>(\n input: { name: TName } & Omit<BooleanInput, \"type\" | \"name\">\n): BooleanInput & { name: TName };\nexport function createBooleanInput<TName extends string>(\n input: Omit<BooleanInput, \"type\" | \"name\">\n): BooleanInput & { name: TName };\nexport function createBooleanInput<TName extends string>(\n input: any\n): BooleanInput & { name: TName } {\n return createInput({\n type: \"boolean\",\n renderer: \"Webiny/Switch\",\n ...input\n }) as BooleanInput & { name: TName };\n}\n\n// Color\nexport function createColorInput<TName extends string>(\n input: { name: TName } & Omit<ColorInput, \"type\" | \"name\">\n): ColorInput & { name: TName };\nexport function createColorInput<TName extends string>(\n input: Omit<ColorInput, \"type\" | \"name\">\n): ColorInput & { name: TName };\nexport function createColorInput<TName extends string>(input: any): ColorInput & { name: TName } {\n return createInput({\n type: \"color\",\n renderer: \"Webiny/ColorPicker\",\n ...input\n }) as ColorInput & { name: TName };\n}\n\n// File\nexport function createFileInput<TName extends string>(\n input: { name: TName } & Omit<FileInput, \"type\" | \"name\">\n): FileInput & { name: TName };\nexport function createFileInput<TName extends string>(\n input: Omit<FileInput, \"type\" | \"name\">\n): FileInput & { name: TName };\nexport function createFileInput<TName extends string>(input: any): FileInput & { name: TName } {\n return createInput({\n type: \"file\",\n renderer: \"Webiny/FileManager\",\n ...input\n }) as FileInput & { name: TName };\n}\n\n// Date\nexport function createDateInput<TName extends string>(\n input: { name: TName } & Omit<DateTimeInput, \"type\" | \"name\">\n): DateTimeInput & { name: TName };\nexport function createDateInput<TName extends string>(\n input: Omit<DateTimeInput, \"type\" | \"name\">\n): DateTimeInput & { name: TName };\nexport function createDateInput<TName extends string>(input: any): DateTimeInput & { name: TName } {\n return createInput({\n type: \"datetime\",\n renderer: \"Webiny/DateTime\",\n ...input\n }) as DateTimeInput & { name: TName };\n}\n\n// Rich Text\nexport function createLexicalInput<TName extends string>(\n input: { name: TName } & Omit<LexicalInput, \"type\" | \"name\">\n): LexicalInput & { name: TName };\nexport function createLexicalInput<TName extends string>(\n input: Omit<LexicalInput, \"type\" | \"name\">\n): LexicalInput & { name: TName };\nexport function createLexicalInput<TName extends string>(\n input: any\n): LexicalInput & { name: TName } {\n return createInput({\n type: \"lexical\",\n renderer: \"Webiny/Lexical\",\n ...input\n }) as LexicalInput & { name: TName };\n}\n\n// Select\nexport function createSelectInput<TName extends string>(\n input: { name: TName } & Omit<SelectInput, \"type\" | \"name\">\n): SelectInput & { name: TName };\nexport function createSelectInput<TName extends string>(\n input: Omit<SelectInput, \"type\" | \"name\">\n): SelectInput & { name: TName };\nexport function createSelectInput<TName extends string>(input: any): SelectInput & { name: TName } {\n return createInput({\n type: \"select\",\n renderer: \"Webiny/Select\",\n ...input\n }) as SelectInput & { name: TName };\n}\n\n// Radio\nexport function createRadioInput<TName extends string>(\n input: { name: TName } & Omit<RadioInput, \"type\" | \"name\">\n): RadioInput & { name: TName };\nexport function createRadioInput<TName extends string>(\n input: Omit<RadioInput, \"type\" | \"name\">\n): RadioInput & { name: TName };\nexport function createRadioInput<TName extends string>(input: any): RadioInput & { name: TName } {\n return createInput({\n type: \"radio\",\n renderer: \"Webiny/RadioGroup\",\n ...input\n }) as RadioInput & { name: TName };\n}\n\n// Object\nexport function createObjectInput<TName extends string>(\n input: { name: TName } & Omit<ObjectInput, \"type\" | \"name\">\n): ObjectInput & { name: TName };\nexport function createObjectInput<TName extends string>(\n input: Omit<ObjectInput, \"type\" | \"name\">\n): ObjectInput & { name: TName };\nexport function createObjectInput<TName extends string>(input: any): ObjectInput & { name: TName } {\n return createInput({\n type: \"object\",\n renderer: \"Webiny/Object\",\n ...input\n }) as ObjectInput & { name: TName };\n}\n\n// Tags\nexport function createTagsInput<TName extends string>(\n input: { name: TName } & Omit<TagsInput, \"type\" | \"name\">\n): TagsInput & { name: TName };\nexport function createTagsInput<TName extends string>(\n input: Omit<TagsInput, \"type\" | \"name\">\n): TagsInput & { name: TName };\nexport function createTagsInput<TName extends string>(input: any): TagsInput & { name: TName } {\n return createInput({\n type: \"text\",\n list: true,\n renderer: \"Webiny/Tags\",\n ...input\n }) as TagsInput & { name: TName };\n}\n\nexport function createSlotInput<TName extends string>(\n input: { name: TName } & Omit<SlotInput, \"type\" | \"name\">\n): SlotInput & { name: TName };\nexport function createSlotInput<TName extends string>(\n input: Omit<SlotInput, \"type\" | \"name\">\n): SlotInput & { name: TName };\nexport function createSlotInput<TName extends string>(input: any): SlotInput & { name: TName } {\n return createInput({\n type: \"slot\",\n list: true,\n renderer: \"Webiny/Slot\",\n defaultValue: [],\n ...input\n }) as SlotInput & { name: TName };\n}\n\n// Content Entry\nexport function createContentEntryInput<TName extends string>(\n input: { name: TName } & Omit<ContentEntryInput, \"type\" | \"name\">\n): ContentEntryInput & { name: TName };\nexport function createContentEntryInput<TName extends string>(\n input: Omit<ContentEntryInput, \"type\" | \"name\">\n): ContentEntryInput & { name: TName };\nexport function createContentEntryInput<TName extends string>(\n input: any\n): ContentEntryInput & { name: TName } {\n return createInput({\n type: \"contentEntry\",\n renderer: \"Webiny/ContentEntry\",\n ...input\n }) as ContentEntryInput & { name: TName };\n}\n\n// Implementation\nexport function createInput(input: ComponentInput): ComponentInput {\n if (input.onChange) {\n // @ts-expect-error We don't use this function on the frontend, so this is ok.\n input.onChange = functionConverter.serialize(input.onChange!);\n }\n return input;\n}\n"],"names":["createTextInput","input","createInput","createLongTextInput","createNumberInput","createBooleanInput","createColorInput","createFileInput","createDateInput","createLexicalInput","createSelectInput","createRadioInput","createObjectInput","createTagsInput","createSlotInput","createContentEntryInput","functionConverter"],"mappings":";AA4DO,SAASA,gBAAsCC,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASE,oBACZF,KAAU;IAEV,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASG,kBAAwCH,KAAU;IAC9D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASI,mBACZJ,KAAU;IAEV,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASK,iBAAuCL,KAAU;IAC7D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASM,gBAAsCN,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASO,gBAAsCP,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASQ,mBACZR,KAAU;IAEV,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASS,kBAAwCT,KAAU;IAC9D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASU,iBAAuCV,KAAU;IAC7D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASW,kBAAwCX,KAAU;IAC9D,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AASO,SAASY,gBAAsCZ,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AAQO,SAASa,gBAAsCb,KAAU;IAC5D,OAAOC,YAAY;QACf,MAAM;QACN,MAAM;QACN,UAAU;QACV,cAAc,EAAE;QAChB,GAAGD,KAAK;IACZ;AACJ;AASO,SAASc,wBACZd,KAAU;IAEV,OAAOC,YAAY;QACf,MAAM;QACN,UAAU;QACV,GAAGD,KAAK;IACZ;AACJ;AAGO,SAASC,YAAYD,KAAqB;IAC7C,IAAIA,MAAM,QAAQ,EAEdA,MAAM,QAAQ,GAAGe,kBAAkB,SAAS,CAACf,MAAM,QAAQ;IAE/D,OAAOA;AACX"}
@@ -16,6 +16,8 @@ export declare class ApiClient {
16
16
  private readonly config;
17
17
  constructor(config: ApiClientConfig);
18
18
  fetch({ headers, path, ...params }: WithPath<RequestInit>): Promise<any>;
19
- query({ query, variables }: QueryParams): Promise<any>;
19
+ query({ query, variables }: QueryParams, options?: {
20
+ noStore?: boolean;
21
+ }): Promise<any>;
20
22
  }
21
23
  export {};
@@ -14,8 +14,10 @@ class ApiClient {
14
14
  ...params
15
15
  }).then((res)=>res.json());
16
16
  }
17
- async query({ query, variables }) {
18
- const fetchOptions = {
17
+ async query({ query, variables }, options) {
18
+ const fetchOptions = options?.noStore ? {
19
+ cache: "no-store"
20
+ } : {
19
21
  next: {
20
22
  revalidate: this.config.preview ? 0 : 60
21
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dataProviders/ApiClient.js","sources":["../../src/dataProviders/ApiClient.ts"],"sourcesContent":["interface QueryParams {\n query: string;\n variables: Record<string, any>;\n}\n\nexport interface ApiClientConfig {\n apiHost: string;\n apiKey: string;\n apiTenant: string;\n preview?: boolean;\n}\n\ntype WithPath<T> = T & {\n path?: string;\n next?: Record<string, any>;\n};\n\nexport class ApiClient {\n private readonly config: ApiClientConfig;\n\n constructor(config: ApiClientConfig) {\n this.config = config;\n }\n\n async fetch({ headers, path, ...params }: WithPath<RequestInit>): Promise<any> {\n return fetch(`${this.config.apiHost}${path}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Tenant\": this.config.apiTenant,\n Authorization: \"Bearer \" + this.config.apiKey,\n ...headers\n },\n ...params\n }).then(res => res.json());\n }\n\n async query({ query, variables }: QueryParams) {\n const fetchOptions = {\n next: {\n revalidate: this.config.preview ? 0 : 60\n }\n };\n\n const request: WithPath<RequestInit> = {\n ...fetchOptions,\n path: \"/graphql\",\n method: \"POST\",\n body: JSON.stringify({\n query,\n variables\n })\n };\n\n const json = await this.fetch(request);\n\n if (json.message) {\n throw new Error(json.message);\n }\n\n if (json.errors) {\n console.error(json.errors);\n throw new Error(\"Failed to fetch API\");\n }\n\n return json.data;\n }\n}\n"],"names":["ApiClient","config","headers","path","params","fetch","res","query","variables","fetchOptions","request","JSON","json","Error","console"],"mappings":"AAiBO,MAAMA;IAGT,YAAYC,MAAuB,CAAE;QACjC,IAAI,CAAC,MAAM,GAAGA;IAClB;IAEA,MAAM,MAAM,EAAEC,OAAO,EAAEC,IAAI,EAAE,GAAGC,QAA+B,EAAgB;QAC3E,OAAOC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,GAAGF,MAAM,EAAE;YAC1C,QAAQ;YACR,SAAS;gBACL,gBAAgB;gBAChB,YAAY,IAAI,CAAC,MAAM,CAAC,SAAS;gBACjC,eAAe,YAAY,IAAI,CAAC,MAAM,CAAC,MAAM;gBAC7C,GAAGD,OAAO;YACd;YACA,GAAGE,MAAM;QACb,GAAG,IAAI,CAACE,CAAAA,MAAOA,IAAI,IAAI;IAC3B;IAEA,MAAM,MAAM,EAAEC,KAAK,EAAEC,SAAS,EAAe,EAAE;QAC3C,MAAMC,eAAe;YACjB,MAAM;gBACF,YAAY,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,IAAI;YAC1C;QACJ;QAEA,MAAMC,UAAiC;YACnC,GAAGD,YAAY;YACf,MAAM;YACN,QAAQ;YACR,MAAME,KAAK,SAAS,CAAC;gBACjBJ;gBACAC;YACJ;QACJ;QAEA,MAAMI,OAAO,MAAM,IAAI,CAAC,KAAK,CAACF;QAE9B,IAAIE,KAAK,OAAO,EACZ,MAAM,IAAIC,MAAMD,KAAK,OAAO;QAGhC,IAAIA,KAAK,MAAM,EAAE;YACbE,QAAQ,KAAK,CAACF,KAAK,MAAM;YACzB,MAAM,IAAIC,MAAM;QACpB;QAEA,OAAOD,KAAK,IAAI;IACpB;AACJ"}
1
+ {"version":3,"file":"dataProviders/ApiClient.js","sources":["../../src/dataProviders/ApiClient.ts"],"sourcesContent":["interface QueryParams {\n query: string;\n variables: Record<string, any>;\n}\n\nexport interface ApiClientConfig {\n apiHost: string;\n apiKey: string;\n apiTenant: string;\n preview?: boolean;\n}\n\ntype WithPath<T> = T & {\n path?: string;\n next?: Record<string, any>;\n};\n\nexport class ApiClient {\n private readonly config: ApiClientConfig;\n\n constructor(config: ApiClientConfig) {\n this.config = config;\n }\n\n async fetch({ headers, path, ...params }: WithPath<RequestInit>): Promise<any> {\n return fetch(`${this.config.apiHost}${path}`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Tenant\": this.config.apiTenant,\n Authorization: \"Bearer \" + this.config.apiKey,\n ...headers\n },\n ...params\n }).then(res => res.json());\n }\n\n async query({ query, variables }: QueryParams, options?: { noStore?: boolean }) {\n const fetchOptions: Pick<WithPath<RequestInit>, \"cache\" | \"next\"> = options?.noStore\n ? { cache: \"no-store\" }\n : { next: { revalidate: this.config.preview ? 0 : 60 } };\n\n const request: WithPath<RequestInit> = {\n ...fetchOptions,\n path: \"/graphql\",\n method: \"POST\",\n body: JSON.stringify({\n query,\n variables\n })\n };\n\n const json = await this.fetch(request);\n\n if (json.message) {\n throw new Error(json.message);\n }\n\n if (json.errors) {\n console.error(json.errors);\n throw new Error(\"Failed to fetch API\");\n }\n\n return json.data;\n }\n}\n"],"names":["ApiClient","config","headers","path","params","fetch","res","query","variables","options","fetchOptions","request","JSON","json","Error","console"],"mappings":"AAiBO,MAAMA;IAGT,YAAYC,MAAuB,CAAE;QACjC,IAAI,CAAC,MAAM,GAAGA;IAClB;IAEA,MAAM,MAAM,EAAEC,OAAO,EAAEC,IAAI,EAAE,GAAGC,QAA+B,EAAgB;QAC3E,OAAOC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,GAAGF,MAAM,EAAE;YAC1C,QAAQ;YACR,SAAS;gBACL,gBAAgB;gBAChB,YAAY,IAAI,CAAC,MAAM,CAAC,SAAS;gBACjC,eAAe,YAAY,IAAI,CAAC,MAAM,CAAC,MAAM;gBAC7C,GAAGD,OAAO;YACd;YACA,GAAGE,MAAM;QACb,GAAG,IAAI,CAACE,CAAAA,MAAOA,IAAI,IAAI;IAC3B;IAEA,MAAM,MAAM,EAAEC,KAAK,EAAEC,SAAS,EAAe,EAAEC,OAA+B,EAAE;QAC5E,MAAMC,eAA8DD,SAAS,UACvE;YAAE,OAAO;QAAW,IACpB;YAAE,MAAM;gBAAE,YAAY,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,IAAI;YAAG;QAAE;QAE3D,MAAME,UAAiC;YACnC,GAAGD,YAAY;YACf,MAAM;YACN,QAAQ;YACR,MAAME,KAAK,SAAS,CAAC;gBACjBL;gBACAC;YACJ;QACJ;QAEA,MAAMK,OAAO,MAAM,IAAI,CAAC,KAAK,CAACF;QAE9B,IAAIE,KAAK,OAAO,EACZ,MAAM,IAAIC,MAAMD,KAAK,OAAO;QAGhC,IAAIA,KAAK,MAAM,EAAE;YACbE,QAAQ,KAAK,CAACF,KAAK,MAAM;YACzB,MAAM,IAAIC,MAAM;QACpB;QAEA,OAAOD,KAAK,IAAI;IACpB;AACJ"}
@@ -1 +1 @@
1
- export declare const GET_PAGE_BY_ID = "\n query GetPageById($id: ID!) {\n websiteBuilder {\n getPageById(id: $id) {\n data {\n id\n version\n properties\n metadata\n elements\n bindings\n extensions\n languagePaths\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n";
1
+ export declare const GET_PAGE_BY_ID = "\n query GetPageById($id: ID!) {\n websiteBuilder {\n getPageById(id: $id) {\n data {\n id\n properties\n elements\n bindings\n extensions\n languagePaths\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n";
@@ -4,9 +4,7 @@ const GET_PAGE_BY_ID = `
4
4
  getPageById(id: $id) {
5
5
  data {
6
6
  id
7
- version
8
7
  properties
9
- metadata
10
8
  elements
11
9
  bindings
12
10
  extensions
@@ -1 +1 @@
1
- {"version":3,"file":"dataProviders/GET_PAGE_BY_ID.js","sources":["../../src/dataProviders/GET_PAGE_BY_ID.ts"],"sourcesContent":["export const GET_PAGE_BY_ID = /* GraphQL*/ `\n query GetPageById($id: ID!) {\n websiteBuilder {\n getPageById(id: $id) {\n data {\n id\n version\n properties\n metadata\n elements\n bindings\n extensions\n languagePaths\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n`;\n"],"names":["GET_PAGE_BY_ID"],"mappings":"AAAO,MAAMA,iBAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;AAsB5C,CAAC"}
1
+ {"version":3,"file":"dataProviders/GET_PAGE_BY_ID.js","sources":["../../src/dataProviders/GET_PAGE_BY_ID.ts"],"sourcesContent":["export const GET_PAGE_BY_ID = /* GraphQL*/ `\n query GetPageById($id: ID!) {\n websiteBuilder {\n getPageById(id: $id) {\n data {\n id\n properties\n elements\n bindings\n extensions\n languagePaths\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n`;\n"],"names":["GET_PAGE_BY_ID"],"mappings":"AAAO,MAAMA,iBAA8B,CAAC;;;;;;;;;;;;;;;;;;;;AAoB5C,CAAC"}
@@ -1 +1 @@
1
- export declare const GET_PAGE_BY_PATH = "\n query GetPageByPath($path: String!) {\n websiteBuilder {\n getPageByPath(path: $path) {\n data {\n id\n version\n properties\n metadata\n elements\n bindings\n extensions\n languagePaths\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n";
1
+ export declare const GET_PAGE_BY_PATH = "\n query GetPageByPath($path: String!) {\n websiteBuilder {\n getPageByPath(path: $path) {\n data {\n id\n properties\n elements\n bindings\n extensions\n languagePaths\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n";
@@ -4,9 +4,7 @@ const GET_PAGE_BY_PATH = `
4
4
  getPageByPath(path: $path) {
5
5
  data {
6
6
  id
7
- version
8
7
  properties
9
- metadata
10
8
  elements
11
9
  bindings
12
10
  extensions
@@ -1 +1 @@
1
- {"version":3,"file":"dataProviders/GET_PAGE_BY_PATH.js","sources":["../../src/dataProviders/GET_PAGE_BY_PATH.ts"],"sourcesContent":["export const GET_PAGE_BY_PATH = /* GraphQL*/ `\n query GetPageByPath($path: String!) {\n websiteBuilder {\n getPageByPath(path: $path) {\n data {\n id\n version\n properties\n metadata\n elements\n bindings\n extensions\n languagePaths\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n`;\n"],"names":["GET_PAGE_BY_PATH"],"mappings":"AAAO,MAAMA,mBAAgC,CAAC;;;;;;;;;;;;;;;;;;;;;;AAsB9C,CAAC"}
1
+ {"version":3,"file":"dataProviders/GET_PAGE_BY_PATH.js","sources":["../../src/dataProviders/GET_PAGE_BY_PATH.ts"],"sourcesContent":["export const GET_PAGE_BY_PATH = /* GraphQL*/ `\n query GetPageByPath($path: String!) {\n websiteBuilder {\n getPageByPath(path: $path) {\n data {\n id\n properties\n elements\n bindings\n extensions\n languagePaths\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n`;\n"],"names":["GET_PAGE_BY_PATH"],"mappings":"AAAO,MAAMA,mBAAgC,CAAC;;;;;;;;;;;;;;;;;;;;AAoB9C,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Pure geometry core for crop + focal point rendering.
3
+ *
4
+ * This module is the single source of truth shared by:
5
+ * - the Admin editor previews (square / 4:3 / 16:9),
6
+ * - the Next.js render helper (`getImageProps` / `<Image>`),
7
+ * - and any future URL-transform loader (`?rect=…`).
8
+ *
9
+ * Keeping the math here guarantees that what an editor previews is exactly what
10
+ * a visitor sees. Everything is framework-agnostic and side-effect free.
11
+ */
12
+ import type { AssetCrop, AssetImage } from "@webiny/sdk";
13
+ import type { AspectRatioInput } from "./types.js";
14
+ /** A rectangle in normalized (0..1) coordinates of the original image. */
15
+ export interface NormalizedRect {
16
+ x: number;
17
+ y: number;
18
+ width: number;
19
+ height: number;
20
+ }
21
+ /**
22
+ * CSS needed to render an image cropped to a target aspect ratio while honoring
23
+ * both the crop rectangle and the focal point — without any server-side processing.
24
+ *
25
+ * `objectPosition` is the simpler, focal-point-only path (apply to an `object-fit:
26
+ * cover` image). `container` + `image` is the accurate path that also honors the
27
+ * crop rectangle via an overflow-clipped wrapper.
28
+ */
29
+ export interface ImageRenderData {
30
+ rect: NormalizedRect;
31
+ objectPosition: string;
32
+ container: {
33
+ position: "relative";
34
+ width: "100%";
35
+ aspectRatio: string;
36
+ overflow: "hidden";
37
+ };
38
+ image: {
39
+ position: "absolute";
40
+ width: string;
41
+ height: string;
42
+ left: string;
43
+ top: string;
44
+ maxWidth: "none";
45
+ objectFit: "cover";
46
+ objectPosition: string;
47
+ };
48
+ intrinsicWidth: number;
49
+ intrinsicHeight: number;
50
+ }
51
+ export declare function resolveAssetImage(override?: AssetImage | null, assetDefault?: AssetImage | null): AssetImage;
52
+ export declare function getCropRect(crop?: AssetCrop | null): NormalizedRect;
53
+ export declare function getVisibleRect(image: AssetImage, aspectRatio?: AspectRatioInput): NormalizedRect;
54
+ export declare function getImageRenderData(image: AssetImage, aspectRatio?: AspectRatioInput): ImageRenderData;
@@ -0,0 +1,112 @@
1
+ const clamp = (value, min = 0, max = 1)=>{
2
+ if (Number.isNaN(value)) return min;
3
+ return Math.min(max, Math.max(min, value));
4
+ };
5
+ const round = (n)=>Math.round(1000 * n) / 1000;
6
+ const DEFAULT_CROP = {
7
+ top: 0,
8
+ left: 0,
9
+ bottom: 0,
10
+ right: 0
11
+ };
12
+ const DEFAULT_FOCAL_POINT = {
13
+ x: 0.5,
14
+ y: 0.5
15
+ };
16
+ function resolveAssetImage(override, assetDefault) {
17
+ return {
18
+ crop: override?.crop ?? assetDefault?.crop,
19
+ focalPoint: override?.focalPoint ?? assetDefault?.focalPoint,
20
+ alt: override?.alt ?? assetDefault?.alt,
21
+ caption: override?.caption ?? assetDefault?.caption
22
+ };
23
+ }
24
+ function getCropRect(crop) {
25
+ const top = clamp(crop?.top ?? DEFAULT_CROP.top);
26
+ const left = clamp(crop?.left ?? DEFAULT_CROP.left);
27
+ const bottom = clamp(crop?.bottom ?? DEFAULT_CROP.bottom);
28
+ const right = clamp(crop?.right ?? DEFAULT_CROP.right);
29
+ const width = Math.max(0, 1 - left - right);
30
+ const height = Math.max(0, 1 - top - bottom);
31
+ return {
32
+ x: left,
33
+ y: top,
34
+ width,
35
+ height
36
+ };
37
+ }
38
+ const resolveAspectRatio = (aspectRatio)=>{
39
+ if ("number" == typeof aspectRatio) return aspectRatio > 0 ? aspectRatio : 1;
40
+ const { width, height } = aspectRatio;
41
+ return width > 0 && height > 0 ? width / height : 1;
42
+ };
43
+ const getFocalCenter = (fp)=>({
44
+ x: clamp(fp?.x ?? DEFAULT_FOCAL_POINT.x),
45
+ y: clamp(fp?.y ?? DEFAULT_FOCAL_POINT.y)
46
+ });
47
+ function getVisibleRect(image, aspectRatio) {
48
+ const cropRect = getCropRect(image.crop);
49
+ if (void 0 === aspectRatio || cropRect.width <= 0 || cropRect.height <= 0) return cropRect;
50
+ const iw = (image.width ?? 0) > 0 ? image.width : 1;
51
+ const ih = (image.height ?? 0) > 0 ? image.height : 1;
52
+ const targetAR = resolveAspectRatio(aspectRatio);
53
+ const cropWpx = cropRect.width * iw;
54
+ const cropHpx = cropRect.height * ih;
55
+ const cropAR = cropWpx / cropHpx;
56
+ let finalWpx;
57
+ let finalHpx;
58
+ if (targetAR > cropAR) {
59
+ finalWpx = cropWpx;
60
+ finalHpx = cropWpx / targetAR;
61
+ } else {
62
+ finalHpx = cropHpx;
63
+ finalWpx = cropHpx * targetAR;
64
+ }
65
+ const finalW = finalWpx / iw;
66
+ const finalH = finalHpx / ih;
67
+ const center = getFocalCenter(image.focalPoint);
68
+ const maxX = cropRect.x + cropRect.width - finalW;
69
+ const maxY = cropRect.y + cropRect.height - finalH;
70
+ const x = clamp(center.x - finalW / 2, cropRect.x, Math.max(cropRect.x, maxX));
71
+ const y = clamp(center.y - finalH / 2, cropRect.y, Math.max(cropRect.y, maxY));
72
+ return {
73
+ x,
74
+ y,
75
+ width: finalW,
76
+ height: finalH
77
+ };
78
+ }
79
+ function getImageRenderData(image, aspectRatio) {
80
+ const rect = getVisibleRect(image, aspectRatio);
81
+ const center = getFocalCenter(image.focalPoint);
82
+ const safeW = rect.width > 0 ? rect.width : 1;
83
+ const safeH = rect.height > 0 ? rect.height : 1;
84
+ const iw = image.width || 1;
85
+ const ih = image.height || 1;
86
+ const boxAspectRatio = void 0 !== aspectRatio ? resolveAspectRatio(aspectRatio) : rect.width * iw / (rect.height * ih);
87
+ return {
88
+ rect,
89
+ objectPosition: `${round(100 * center.x)}% ${round(100 * center.y)}%`,
90
+ container: {
91
+ position: "relative",
92
+ width: "100%",
93
+ aspectRatio: `${round(boxAspectRatio)}`,
94
+ overflow: "hidden"
95
+ },
96
+ image: {
97
+ position: "absolute",
98
+ width: `${round(100 / safeW)}%`,
99
+ height: `${round(100 / safeH)}%`,
100
+ left: `${round(100 * -(rect.x / safeW))}%`,
101
+ top: `${round(100 * -(rect.y / safeH))}%`,
102
+ maxWidth: "none",
103
+ objectFit: "cover",
104
+ objectPosition: `${round(100 * center.x)}% ${round(100 * center.y)}%`
105
+ },
106
+ intrinsicWidth: iw,
107
+ intrinsicHeight: ih
108
+ };
109
+ }
110
+ export { getCropRect, getImageRenderData, getVisibleRect, resolveAssetImage };
111
+
112
+ //# sourceMappingURL=geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image/geometry.js","sources":["../../src/image/geometry.ts"],"sourcesContent":["/**\n * Pure geometry core for crop + focal point rendering.\n *\n * This module is the single source of truth shared by:\n * - the Admin editor previews (square / 4:3 / 16:9),\n * - the Next.js render helper (`getImageProps` / `<Image>`),\n * - and any future URL-transform loader (`?rect=…`).\n *\n * Keeping the math here guarantees that what an editor previews is exactly what\n * a visitor sees. Everything is framework-agnostic and side-effect free.\n */\nimport type { AssetCrop, AssetFocalPoint, AssetImage } from \"@webiny/sdk\";\nimport type { AspectRatioInput } from \"./types.js\";\n\n/** A rectangle in normalized (0..1) coordinates of the original image. */\nexport interface NormalizedRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * CSS needed to render an image cropped to a target aspect ratio while honoring\n * both the crop rectangle and the focal point — without any server-side processing.\n *\n * `objectPosition` is the simpler, focal-point-only path (apply to an `object-fit:\n * cover` image). `container` + `image` is the accurate path that also honors the\n * crop rectangle via an overflow-clipped wrapper.\n */\nexport interface ImageRenderData {\n rect: NormalizedRect;\n objectPosition: string;\n container: {\n position: \"relative\";\n width: \"100%\";\n aspectRatio: string;\n overflow: \"hidden\";\n };\n image: {\n position: \"absolute\";\n width: string;\n height: string;\n left: string;\n top: string;\n maxWidth: \"none\";\n objectFit: \"cover\";\n objectPosition: string;\n };\n intrinsicWidth: number;\n intrinsicHeight: number;\n}\n\nconst clamp = (value: number, min = 0, max = 1): number => {\n if (Number.isNaN(value)) {\n return min;\n }\n return Math.min(max, Math.max(min, value));\n};\n\nconst round = (n: number): number => Math.round(n * 1000) / 1000;\n\nconst DEFAULT_CROP: AssetCrop = { top: 0, left: 0, bottom: 0, right: 0 };\nconst DEFAULT_FOCAL_POINT: AssetFocalPoint = { x: 0.5, y: 0.5 };\n\nexport function resolveAssetImage(\n override?: AssetImage | null,\n assetDefault?: AssetImage | null\n): AssetImage {\n return {\n crop: override?.crop ?? assetDefault?.crop,\n focalPoint: override?.focalPoint ?? assetDefault?.focalPoint,\n alt: override?.alt ?? assetDefault?.alt,\n caption: override?.caption ?? assetDefault?.caption\n };\n}\n\nexport function getCropRect(crop?: AssetCrop | null): NormalizedRect {\n const top = clamp(crop?.top ?? DEFAULT_CROP.top);\n const left = clamp(crop?.left ?? DEFAULT_CROP.left);\n const bottom = clamp(crop?.bottom ?? DEFAULT_CROP.bottom);\n const right = clamp(crop?.right ?? DEFAULT_CROP.right);\n\n const width = Math.max(0, 1 - left - right);\n const height = Math.max(0, 1 - top - bottom);\n\n return { x: left, y: top, width, height };\n}\n\nconst resolveAspectRatio = (aspectRatio: AspectRatioInput): number => {\n if (typeof aspectRatio === \"number\") {\n return aspectRatio > 0 ? aspectRatio : 1;\n }\n const { width, height } = aspectRatio;\n return width > 0 && height > 0 ? width / height : 1;\n};\n\nconst getFocalCenter = (fp?: AssetFocalPoint | null): { x: number; y: number } => ({\n x: clamp(fp?.x ?? DEFAULT_FOCAL_POINT.x),\n y: clamp(fp?.y ?? DEFAULT_FOCAL_POINT.y)\n});\n\nexport function getVisibleRect(image: AssetImage, aspectRatio?: AspectRatioInput): NormalizedRect {\n const cropRect = getCropRect(image.crop);\n\n if (aspectRatio === undefined || cropRect.width <= 0 || cropRect.height <= 0) {\n return cropRect;\n }\n\n const iw = (image.width ?? 0) > 0 ? image.width! : 1;\n const ih = (image.height ?? 0) > 0 ? image.height! : 1;\n const targetAR = resolveAspectRatio(aspectRatio);\n\n const cropWpx = cropRect.width * iw;\n const cropHpx = cropRect.height * ih;\n const cropAR = cropWpx / cropHpx;\n\n let finalWpx: number;\n let finalHpx: number;\n if (targetAR > cropAR) {\n finalWpx = cropWpx;\n finalHpx = cropWpx / targetAR;\n } else {\n finalHpx = cropHpx;\n finalWpx = cropHpx * targetAR;\n }\n\n const finalW = finalWpx / iw;\n const finalH = finalHpx / ih;\n\n const center = getFocalCenter(image.focalPoint);\n const maxX = cropRect.x + cropRect.width - finalW;\n const maxY = cropRect.y + cropRect.height - finalH;\n const x = clamp(center.x - finalW / 2, cropRect.x, Math.max(cropRect.x, maxX));\n const y = clamp(center.y - finalH / 2, cropRect.y, Math.max(cropRect.y, maxY));\n\n return { x, y, width: finalW, height: finalH };\n}\n\nexport function getImageRenderData(\n image: AssetImage,\n aspectRatio?: AspectRatioInput\n): ImageRenderData {\n const rect = getVisibleRect(image, aspectRatio);\n const center = getFocalCenter(image.focalPoint);\n\n const safeW = rect.width > 0 ? rect.width : 1;\n const safeH = rect.height > 0 ? rect.height : 1;\n\n const iw = image.width || 1;\n const ih = image.height || 1;\n\n const boxAspectRatio =\n aspectRatio !== undefined\n ? resolveAspectRatio(aspectRatio)\n : (rect.width * iw) / (rect.height * ih);\n\n return {\n rect,\n objectPosition: `${round(center.x * 100)}% ${round(center.y * 100)}%`,\n container: {\n position: \"relative\",\n width: \"100%\",\n aspectRatio: `${round(boxAspectRatio)}`,\n overflow: \"hidden\"\n },\n image: {\n position: \"absolute\",\n width: `${round(100 / safeW)}%`,\n height: `${round(100 / safeH)}%`,\n left: `${round(-(rect.x / safeW) * 100)}%`,\n top: `${round(-(rect.y / safeH) * 100)}%`,\n maxWidth: \"none\",\n objectFit: \"cover\",\n objectPosition: `${round(center.x * 100)}% ${round(center.y * 100)}%`\n },\n intrinsicWidth: iw,\n intrinsicHeight: ih\n };\n}\n"],"names":["clamp","value","min","max","Number","Math","round","n","DEFAULT_CROP","DEFAULT_FOCAL_POINT","resolveAssetImage","override","assetDefault","getCropRect","crop","top","left","bottom","right","width","height","resolveAspectRatio","aspectRatio","getFocalCenter","fp","getVisibleRect","image","cropRect","undefined","iw","ih","targetAR","cropWpx","cropHpx","cropAR","finalWpx","finalHpx","finalW","finalH","center","maxX","maxY","x","y","getImageRenderData","rect","safeW","safeH","boxAspectRatio"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,190 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getCropRect, getImageRenderData, getVisibleRect, resolveAssetImage } from "./geometry.js";
3
+ const geometry_test_image = (overrides)=>({
4
+ width: 1000,
5
+ height: 1000,
6
+ ...overrides
7
+ });
8
+ describe("resolveAssetImage", ()=>{
9
+ it("prefers the override per property, falling back to the asset default", ()=>{
10
+ const assetDefault = {
11
+ crop: {
12
+ top: 0.1,
13
+ left: 0.1,
14
+ bottom: 0.1,
15
+ right: 0.1
16
+ },
17
+ focalPoint: {
18
+ x: 0.2,
19
+ y: 0.2
20
+ },
21
+ alt: "default alt",
22
+ caption: "default caption"
23
+ };
24
+ const override = {
25
+ focalPoint: {
26
+ x: 0.8,
27
+ y: 0.8
28
+ },
29
+ alt: "override alt"
30
+ };
31
+ const result = resolveAssetImage(override, assetDefault);
32
+ expect(result.crop).toEqual(assetDefault.crop);
33
+ expect(result.caption).toBe("default caption");
34
+ expect(result.focalPoint).toEqual(override.focalPoint);
35
+ expect(result.alt).toBe("override alt");
36
+ });
37
+ it("handles null/undefined inputs", ()=>{
38
+ expect(resolveAssetImage(null, null)).toEqual({
39
+ crop: void 0,
40
+ focalPoint: void 0,
41
+ alt: void 0,
42
+ caption: void 0
43
+ });
44
+ });
45
+ });
46
+ describe("getCropRect", ()=>{
47
+ it("returns the full image when no crop is set", ()=>{
48
+ expect(getCropRect()).toEqual({
49
+ x: 0,
50
+ y: 0,
51
+ width: 1,
52
+ height: 1
53
+ });
54
+ });
55
+ it("converts edge insets into a rectangle", ()=>{
56
+ const rect = getCropRect({
57
+ top: 0.1,
58
+ left: 0.2,
59
+ bottom: 0.3,
60
+ right: 0.05
61
+ });
62
+ expect(rect.x).toBeCloseTo(0.2, 10);
63
+ expect(rect.y).toBeCloseTo(0.1, 10);
64
+ expect(rect.width).toBeCloseTo(0.75, 10);
65
+ expect(rect.height).toBeCloseTo(0.6, 10);
66
+ });
67
+ it("does not produce negative dimensions when insets overlap", ()=>{
68
+ const rect = getCropRect({
69
+ top: 0.7,
70
+ left: 0.7,
71
+ bottom: 0.7,
72
+ right: 0.7
73
+ });
74
+ expect(rect.width).toBe(0);
75
+ expect(rect.height).toBe(0);
76
+ });
77
+ it("clamps out-of-range values", ()=>{
78
+ const rect = getCropRect({
79
+ top: -1,
80
+ left: 2,
81
+ bottom: 0,
82
+ right: 0
83
+ });
84
+ expect(rect.x).toBe(1);
85
+ expect(rect.y).toBe(0);
86
+ });
87
+ });
88
+ describe("getVisibleRect", ()=>{
89
+ it("returns the crop rect when no aspect ratio is requested", ()=>{
90
+ const rect = getVisibleRect(geometry_test_image({
91
+ crop: {
92
+ top: 0.1,
93
+ left: 0.1,
94
+ bottom: 0.1,
95
+ right: 0.1
96
+ }
97
+ }));
98
+ expect(rect).toEqual({
99
+ x: 0.1,
100
+ y: 0.1,
101
+ width: 0.8,
102
+ height: 0.8
103
+ });
104
+ });
105
+ it("fits a 16:9 rect inside a square image, centered by default", ()=>{
106
+ const rect = getVisibleRect(geometry_test_image(), 16 / 9);
107
+ expect(rect.width).toBeCloseTo(1, 5);
108
+ expect(rect.height).toBeCloseTo(0.5625, 5);
109
+ expect(rect.y).toBeCloseTo(0.21875, 5);
110
+ expect(rect.x).toBeCloseTo(0, 5);
111
+ });
112
+ it("shifts the visible rect toward the focal point and clamps to bounds", ()=>{
113
+ const rect = getVisibleRect(geometry_test_image({
114
+ focalPoint: {
115
+ x: 0.5,
116
+ y: 0.05
117
+ }
118
+ }), 16 / 9);
119
+ expect(rect.y).toBeCloseTo(0, 5);
120
+ });
121
+ it("keeps the visible rect within the crop rectangle", ()=>{
122
+ const rect = getVisibleRect(geometry_test_image({
123
+ crop: {
124
+ top: 0.25,
125
+ left: 0,
126
+ bottom: 0.25,
127
+ right: 0
128
+ },
129
+ focalPoint: {
130
+ x: 0.5,
131
+ y: 0.9
132
+ }
133
+ }), 1);
134
+ expect(rect.y).toBeGreaterThanOrEqual(0.25 - 1e-9);
135
+ expect(rect.y + rect.height).toBeLessThanOrEqual(0.75 + 1e-9);
136
+ });
137
+ it("accepts an explicit width/height aspect ratio", ()=>{
138
+ const byObject = getVisibleRect(geometry_test_image(), {
139
+ width: 16,
140
+ height: 9
141
+ });
142
+ const byNumber = getVisibleRect(geometry_test_image(), 16 / 9);
143
+ expect(byObject).toEqual(byNumber);
144
+ });
145
+ });
146
+ describe("getImageRenderData", ()=>{
147
+ it("derives objectPosition from the focal point", ()=>{
148
+ const data = getImageRenderData(geometry_test_image({
149
+ focalPoint: {
150
+ x: 0.25,
151
+ y: 0.75
152
+ }
153
+ }), 1);
154
+ expect(data.objectPosition).toBe("25% 75%");
155
+ });
156
+ it("produces an overflow-hidden container with the requested aspect ratio", ()=>{
157
+ const data = getImageRenderData(geometry_test_image(), 16 / 9);
158
+ expect(data.container.overflow).toBe("hidden");
159
+ expect(data.container.aspectRatio).toBe(`${Math.round(16 / 9 * 1000) / 1000}`);
160
+ });
161
+ it("scales and offsets the image so the visible rect fills the container", ()=>{
162
+ const data = getImageRenderData(geometry_test_image(), 16 / 9);
163
+ expect(data.image.width).toBe("100%");
164
+ expect(data.image.height).toBe(`${Math.round(100 / 0.5625 * 1000) / 1000}%`);
165
+ expect(parseFloat(data.image.left)).toBe(0);
166
+ expect(parseFloat(data.image.top)).toBeLessThan(0);
167
+ });
168
+ it("stays finite for a degenerate (collapsed) crop", ()=>{
169
+ const data = getImageRenderData(geometry_test_image({
170
+ crop: {
171
+ top: 0.7,
172
+ left: 0.7,
173
+ bottom: 0.7,
174
+ right: 0.7
175
+ }
176
+ }), 1);
177
+ expect(Number.isFinite(parseFloat(data.image.width))).toBe(true);
178
+ expect(Number.isFinite(parseFloat(data.image.height))).toBe(true);
179
+ });
180
+ it("passes through intrinsic dimensions", ()=>{
181
+ const data = getImageRenderData(geometry_test_image({
182
+ width: 1920,
183
+ height: 1080
184
+ }), 16 / 9);
185
+ expect(data.intrinsicWidth).toBe(1920);
186
+ expect(data.intrinsicHeight).toBe(1080);
187
+ });
188
+ });
189
+
190
+ //# sourceMappingURL=geometry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image/geometry.test.js","sources":["../../src/image/geometry.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport { getCropRect, getImageRenderData, getVisibleRect, resolveAssetImage } from \"./geometry.js\";\nimport type { AssetImage } from \"@webiny/sdk\";\n\nconst image = (overrides?: Partial<AssetImage>): AssetImage => ({\n width: 1000,\n height: 1000,\n ...overrides\n});\n\ndescribe(\"resolveAssetImage\", () => {\n it(\"prefers the override per property, falling back to the asset default\", () => {\n const assetDefault: AssetImage = {\n crop: { top: 0.1, left: 0.1, bottom: 0.1, right: 0.1 },\n focalPoint: { x: 0.2, y: 0.2 },\n alt: \"default alt\",\n caption: \"default caption\"\n };\n const override: AssetImage = {\n focalPoint: { x: 0.8, y: 0.8 },\n alt: \"override alt\"\n };\n\n const result = resolveAssetImage(override, assetDefault);\n\n expect(result.crop).toEqual(assetDefault.crop);\n expect(result.caption).toBe(\"default caption\");\n expect(result.focalPoint).toEqual(override.focalPoint);\n expect(result.alt).toBe(\"override alt\");\n });\n\n it(\"handles null/undefined inputs\", () => {\n expect(resolveAssetImage(null, null)).toEqual({\n crop: undefined,\n focalPoint: undefined,\n alt: undefined,\n caption: undefined\n });\n });\n});\n\ndescribe(\"getCropRect\", () => {\n it(\"returns the full image when no crop is set\", () => {\n expect(getCropRect()).toEqual({ x: 0, y: 0, width: 1, height: 1 });\n });\n\n it(\"converts edge insets into a rectangle\", () => {\n const rect = getCropRect({ top: 0.1, left: 0.2, bottom: 0.3, right: 0.05 });\n expect(rect.x).toBeCloseTo(0.2, 10);\n expect(rect.y).toBeCloseTo(0.1, 10);\n expect(rect.width).toBeCloseTo(0.75, 10);\n expect(rect.height).toBeCloseTo(0.6, 10);\n });\n\n it(\"does not produce negative dimensions when insets overlap\", () => {\n const rect = getCropRect({ top: 0.7, left: 0.7, bottom: 0.7, right: 0.7 });\n expect(rect.width).toBe(0);\n expect(rect.height).toBe(0);\n });\n\n it(\"clamps out-of-range values\", () => {\n const rect = getCropRect({ top: -1, left: 2, bottom: 0, right: 0 });\n expect(rect.x).toBe(1);\n expect(rect.y).toBe(0);\n });\n});\n\ndescribe(\"getVisibleRect\", () => {\n it(\"returns the crop rect when no aspect ratio is requested\", () => {\n const rect = getVisibleRect(\n image({ crop: { top: 0.1, left: 0.1, bottom: 0.1, right: 0.1 } })\n );\n expect(rect).toEqual({ x: 0.1, y: 0.1, width: 0.8, height: 0.8 });\n });\n\n it(\"fits a 16:9 rect inside a square image, centered by default\", () => {\n const rect = getVisibleRect(image(), 16 / 9);\n expect(rect.width).toBeCloseTo(1, 5);\n expect(rect.height).toBeCloseTo(0.5625, 5);\n expect(rect.y).toBeCloseTo((1 - 0.5625) / 2, 5);\n expect(rect.x).toBeCloseTo(0, 5);\n });\n\n it(\"shifts the visible rect toward the focal point and clamps to bounds\", () => {\n const rect = getVisibleRect(image({ focalPoint: { x: 0.5, y: 0.05 } }), 16 / 9);\n expect(rect.y).toBeCloseTo(0, 5);\n });\n\n it(\"keeps the visible rect within the crop rectangle\", () => {\n const rect = getVisibleRect(\n image({\n crop: { top: 0.25, left: 0, bottom: 0.25, right: 0 },\n focalPoint: { x: 0.5, y: 0.9 }\n }),\n 1\n );\n expect(rect.y).toBeGreaterThanOrEqual(0.25 - 1e-9);\n expect(rect.y + rect.height).toBeLessThanOrEqual(0.75 + 1e-9);\n });\n\n it(\"accepts an explicit width/height aspect ratio\", () => {\n const byObject = getVisibleRect(image(), { width: 16, height: 9 });\n const byNumber = getVisibleRect(image(), 16 / 9);\n expect(byObject).toEqual(byNumber);\n });\n});\n\ndescribe(\"getImageRenderData\", () => {\n it(\"derives objectPosition from the focal point\", () => {\n const data = getImageRenderData(image({ focalPoint: { x: 0.25, y: 0.75 } }), 1);\n expect(data.objectPosition).toBe(\"25% 75%\");\n });\n\n it(\"produces an overflow-hidden container with the requested aspect ratio\", () => {\n const data = getImageRenderData(image(), 16 / 9);\n expect(data.container.overflow).toBe(\"hidden\");\n expect(data.container.aspectRatio).toBe(`${Math.round((16 / 9) * 1000) / 1000}`);\n });\n\n it(\"scales and offsets the image so the visible rect fills the container\", () => {\n const data = getImageRenderData(image(), 16 / 9);\n expect(data.image.width).toBe(\"100%\");\n expect(data.image.height).toBe(`${Math.round((100 / 0.5625) * 1000) / 1000}%`);\n expect(parseFloat(data.image.left)).toBe(0);\n expect(parseFloat(data.image.top)).toBeLessThan(0);\n });\n\n it(\"stays finite for a degenerate (collapsed) crop\", () => {\n const data = getImageRenderData(\n image({ crop: { top: 0.7, left: 0.7, bottom: 0.7, right: 0.7 } }),\n 1\n );\n expect(Number.isFinite(parseFloat(data.image.width))).toBe(true);\n expect(Number.isFinite(parseFloat(data.image.height))).toBe(true);\n });\n\n it(\"passes through intrinsic dimensions\", () => {\n const data = getImageRenderData(image({ width: 1920, height: 1080 }), 16 / 9);\n expect(data.intrinsicWidth).toBe(1920);\n expect(data.intrinsicHeight).toBe(1080);\n });\n});\n"],"names":["image","overrides","describe","it","assetDefault","override","result","resolveAssetImage","expect","undefined","getCropRect","rect","getVisibleRect","byObject","byNumber","data","getImageRenderData","Math","parseFloat","Number"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./presets.js";
3
+ export * from "./geometry.js";
package/image/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./presets.js";
3
+ export * from "./geometry.js";
@@ -0,0 +1,6 @@
1
+ import type { AspectRatioPreset } from "./types.js";
2
+ /**
3
+ * Default aspect ratios shown in the image editor's preview strip.
4
+ * A later phase will let a field lock to one of these (or a custom ratio).
5
+ */
6
+ export declare const ASPECT_RATIO_PRESETS: AspectRatioPreset[];
@@ -0,0 +1,20 @@
1
+ const ASPECT_RATIO_PRESETS = [
2
+ {
3
+ id: "square",
4
+ label: "Square",
5
+ ratio: 1
6
+ },
7
+ {
8
+ id: "4:3",
9
+ label: "4:3",
10
+ ratio: 4 / 3
11
+ },
12
+ {
13
+ id: "16:9",
14
+ label: "16:9",
15
+ ratio: 16 / 9
16
+ }
17
+ ];
18
+ export { ASPECT_RATIO_PRESETS };
19
+
20
+ //# sourceMappingURL=presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image/presets.js","sources":["../../src/image/presets.ts"],"sourcesContent":["import type { AspectRatioPreset } from \"./types.js\";\n\n/**\n * Default aspect ratios shown in the image editor's preview strip.\n * A later phase will let a field lock to one of these (or a custom ratio).\n */\nexport const ASPECT_RATIO_PRESETS: AspectRatioPreset[] = [\n { id: \"square\", label: \"Square\", ratio: 1 },\n { id: \"4:3\", label: \"4:3\", ratio: 4 / 3 },\n { id: \"16:9\", label: \"16:9\", ratio: 16 / 9 }\n];\n"],"names":["ASPECT_RATIO_PRESETS"],"mappings":"AAMO,MAAMA,uBAA4C;IACrD;QAAE,IAAI;QAAU,OAAO;QAAU,OAAO;IAAE;IAC1C;QAAE,IAAI;QAAO,OAAO;QAAO,OAAO,IAAI;IAAE;IACxC;QAAE,IAAI;QAAQ,OAAO;QAAQ,OAAO,KAAK;IAAE;CAC9C"}
@@ -0,0 +1,10 @@
1
+ export type { AssetCrop, AssetFocalPoint } from "@webiny/sdk";
2
+ export type AspectRatioInput = number | {
3
+ width: number;
4
+ height: number;
5
+ };
6
+ export interface AspectRatioPreset {
7
+ id: string;
8
+ label: string;
9
+ ratio: number;
10
+ }
package/image/types.js ADDED
File without changes
package/index.d.ts CHANGED
@@ -8,6 +8,12 @@ export * from "./DocumentStore.js";
8
8
  export * from "./Logger.js";
9
9
  export * from "./FunctionConverter.js";
10
10
  export * from "./createInput.js";
11
+ export * from "./contentEntry/resolveContentEntry.js";
12
+ export * from "./contentEntry/ContentEntryCache.js";
13
+ export * from "./contentEntry/ContentEntryBindingsResolver.js";
14
+ export * from "./createComponent.js";
15
+ export * from "./image/index.js";
16
+ export * from "./asset/index.js";
11
17
  export * from "./MouseTracker.js";
12
18
  export * from "./ViewportManager.js";
13
19
  export * from "./messenger/Messenger.js";