diff --git a/README.md b/README.md index 1b5071b..4be796b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -These libraries is a collection of components and services that are useful for developing Ionic Angular applications. +These libraries are a collection of components and services for Ionic Angular applications. Documentation: [Ionic Angular Kit](https://docs.rdlabo.dev/projects/ionic-angular-kit) · [Photo Editor](https://docs.rdlabo.dev/projects/ionic-angular-photo-editor) · [Scroll Header](https://docs.rdlabo.dev/projects/ionic-angular-scroll-header) · [Scroll Strategies](https://docs.rdlabo.dev/projects/ngx-cdk-scroll-strategies) -Migration guide: [Angular 21–22 and Ionic 9](docs/migration.md) +Upgrading from an earlier release? Read the [library migration guide](docs/migration.md). [Demo site is here.](https://rdlabo-ionic-angular-library.netlify.app/) @@ -15,16 +15,16 @@ Migration guide: [Angular 21–22 and Ionic 9](docs/migration.md) | v19 | v8 | 19.x.x | | v18 | v8 | 2.x.x | -Ionic 9 native applications require Capacitor 7 or later. See the [migration guide](docs/migration.md) before upgrading an existing application. +The compatibility table describes package requirements, not application-framework migration steps. Follow the upstream Angular, Ionic, and Capacitor guides separately, then apply this repository's [library-specific migrations](docs/migration.md). ## packages -| package name | description | path | -| ----------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | -| @rdlabo/ionic-angular-kit | Auth guards, Firebase flows, storage, overlay, HTTP interceptor, and other fleet helpers. | [/projects/kit](https://github.com/rdlabo-dev/ionic-angular-library/tree/main/projects/kit#readme) | -| @rdlabo/ionic-angular-photo-editor | This is a photo editor and viewer for modal page of Ionic Angular project using Capacitor. | [/project/photo-editor](https://github.com/rdlabo-dev/ionic-angular-library/tree/main/projects/photo-editor#readme) | -| @rdlabo/ionic-angular-scroll-header | This is directive for scroll with Header. | [/project/scroll-header](https://github.com/rdlabo-dev/ionic-angular-library/tree/main/projects/scroll-header#readme) | -| @rdlabo/ngx-cdk-scroll-strategies | This is directive for virtual scroll of dynamic item size. | [/project/scroll-strategies](https://github.com/rdlabo-dev/ionic-angular-library/tree/main/projects/scroll-strategies#readme) | +| package name | description | path | +| ----------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| @rdlabo/ionic-angular-kit | Auth guards, Firebase flows, storage, overlays, HTTP, and shared utilities. | [/projects/kit](https://github.com/rdlabo-dev/ionic-angular-library/tree/main/projects/kit#readme) | +| @rdlabo/ionic-angular-photo-editor | Photo editor/viewer modals and camera or album file loading. | [/projects/photo-editor](https://github.com/rdlabo-dev/ionic-angular-library/tree/main/projects/photo-editor#readme) | +| @rdlabo/ionic-angular-scroll-header | Directives for scroll-linked Ionic headers. | [/projects/scroll-header](https://github.com/rdlabo-dev/ionic-angular-library/tree/main/projects/scroll-header#readme) | +| @rdlabo/ngx-cdk-scroll-strategies | Dynamic-size virtual scroll strategies for Angular CDK. | [/projects/scroll-strategies](https://github.com/rdlabo-dev/ionic-angular-library/tree/main/projects/scroll-strategies#readme) | ### Release diff --git a/docs/migration.md b/docs/migration.md index 88b9c4f..8f6efe0 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -1,75 +1,98 @@ -# Migration guide +# Library migration guide -## Angular 21–22 and Ionic 9 +This guide covers consumer-facing changes in the packages published from this repository. It does not replace the upstream [Angular update guide](https://angular.dev/update-guide), [Ionic breaking-change guide](https://github.com/ionic-team/ionic-framework/blob/main/BREAKING.md), or [Capacitor upgrade guides](https://capacitorjs.com/docs/updating/overview). -The 22.x package line supports Angular 21 and 22 with Ionic 9. Ionic 9 requires Angular 18 or later; native applications also require Capacitor 7 or later. +## v21 to v22 -Read the upstream [Ionic 9 breaking changes](https://github.com/ionic-team/ionic-framework/blob/main/BREAKING.md#version-9x) and the [Angular version compatibility table](https://angular.dev/reference/versions) before upgrading your application. +### Compatibility requirements -### 1. Update dependencies +Version 22 supports Angular 21 and 22. The kit, photo-editor, and scroll-header packages require Ionic 9; scroll-strategies requires only Angular and Angular CDK. Native kit and photo-editor features require Capacitor 7 or 8. Upgrade the host application with the upstream tools first, then update only the rdlabo packages it uses. Keep rdlabo packages on the same release line when an application uses more than one of them. -Use matching Angular major versions throughout the application. Angular 22 uses TypeScript 6.0 and requires a supported Node.js release. +While v22 is available under the npm `beta` dist-tag, install the prerelease with: ```bash -npx ng update @angular/core@22 @angular/cli@22 -npx @ionic/migrate +npm install @rdlabo/ionic-angular-kit@beta \ + @rdlabo/ionic-angular-photo-editor@beta \ + @rdlabo/ionic-angular-scroll-header@beta \ + @rdlabo/ngx-cdk-scroll-strategies@beta ``` -`@ionic/migrate` is the recommended path for Ionic applications: it updates the Ionic packages and applies the available source migrations. Review its changes together with the manual audit below. The migrator does not support Angular library workspaces such as this repository, so library maintainers must apply the Ionic changes manually. +After stable v22 is published, use `@^22` instead of `@beta`. Omit packages that the application does not use. Release maintainers must update this prerelease instruction when promoting v22 to npm `latest`. -For a native application, upgrade Capacitor separately and follow its migration guide: +### @rdlabo/ionic-angular-photo-editor -```bash -npm install @capacitor/core@^8 @capacitor/ios@^8 @capacitor/android@^8 -npm install --save-dev @capacitor/cli@^8 -npx cap migrate -``` +#### Choose the header button scheme -Capacitor 7 and 8 are supported by these libraries. Keep all Capacitor core and plugin packages on compatible major versions. +`PhotoEditorPage` and `PhotoViewerPage` now require `headerButtonColorScheme`. The library cannot infer the final `ion-toolbar` appearance after application CSS, translucency, and runtime theme overrides are applied. -### 2. Update Ionic Angular imports +Use `dark` for a dark or black toolbar and `light` for a light or white toolbar. Define typed props before passing them to Ionic because `ModalController` does not enforce the component's input types. -Ionic 9 exports standalone components and providers from `@ionic/angular` by default. Replace imports from the old standalone entry point: +```typescript +import { PhotoEditorPage, PhotoEditorProps } from '@rdlabo/ionic-angular-photo-editor'; -```ts -// Before -import { IonContent, ModalController, provideIonicAngular } from '@ionic/angular/standalone'; +const componentProps = { + value, + headerButtonColorScheme: 'dark', +} satisfies PhotoEditorProps; -// After -import { IonContent, ModalController, provideIonicAngular } from '@ionic/angular'; +await modalController.create({ + component: PhotoEditorPage, + componentProps, +}); ``` -If the application intentionally uses lazy-loaded Ionic wrappers, import those wrappers from `@ionic/angular/lazy`. `IonicModule` still works in Ionic 9 but is deprecated; new and migrated applications should use `provideIonicAngular()`. +```typescript +import { PhotoViewerPage, PhotoViewerProps } from '@rdlabo/ionic-angular-photo-editor'; -### 3. Check change detection +const componentProps = { + imageUrls, + headerButtonColorScheme: 'dark', +} satisfies PhotoViewerProps; -Angular 21 is zoneless by default. Angular 22 additionally defaults components without an explicit strategy to `OnPush`. +await modalController.create({ + component: PhotoViewerPage, + componentProps, +}); +``` -Prefer signals for state changed after asynchronous work such as overlay dismissal, timers, RxJS subscriptions, or platform events. Otherwise call `ChangeDetectorRef.markForCheck()`. Run the Angular update migrations so existing components retain their intended change-detection behavior. +`PhotoViewerProps.imageUrls` is now correctly declared as required. It was already a required component input, so ensure every viewer invocation supplies it. -### 4. Audit Ionic 9 component changes +#### Update direct-template selectors -Check application templates, styles, and tests for the following Ionic 9 changes: +The public components now use package-prefixed selectors: -- Replace `autocorrect="on"` or `autocorrect="off"` on `ion-input` and `ion-searchbar` with a boolean property binding, or omit it for the default `false` value. -- Replace legacy picker components and controller APIs with the inline `ion-picker` component. -- Set `handleBehavior="none"` on sheet modals only when the handle must retain its previous inert behavior; the new default is `cycle`. -- Use `ion-router-outlet` for URL-based routing. `ion-nav` now manages only an imperative, URL-less navigation stack. -- Do not rely on `ion-select` emitting `ionChange` when a confirmed value did not change. Use dismissal events when confirmation itself matters. -- Do not rely on the `selected` dismissal role from the `ion-select` action-sheet interface; listen for `ionChange` when the selected value changes. -- Check floating labels on `ion-input`, `ion-select`, and `ion-textarea` with slotted content. Slotted content alone no longer causes the label to float. -- Review custom selectors and shadow-part styles for `ion-input`, `ion-select`, and `ion-textarea`, whose internal structures changed. -- Review Material Design textarea layouts: the new minimum height is 72px. +| Before | After | +| -------------------- | ----------------------- | +| `` | `` | +| `` | `` | -### 5. Verify the application +No selector update is needed when presenting `PhotoEditorPage` or `PhotoViewerPage` by component class through `ModalController`. -Build and test both web and native targets after updating: +#### Optional iOS 26 theme integration -```bash -npm run lint -npm test -npm run build -npx cap sync +Applications using `@rdlabo/ionic-theme-ios26` v3 can import the photo-editor adapter after the Ionic and iOS 26 theme styles: + +```scss +@import '@rdlabo/ionic-theme-ios26/dist/css/ionic-theme-ios26.css'; +@import '@ionic/angular/css/palettes/dark.class.css'; +@import '@rdlabo/ionic-theme-ios26/dist/css/ionic-theme-ios26-dark-class.css'; +@import '@rdlabo/ionic-angular-photo-editor/css/ios26-header-button-color-scheme.css'; ``` -Test modal sheets, form controls, select overlays, virtual scrolling, navigation gestures, and state updates that occur after asynchronous callbacks on each supported platform. +Do not import the adapter when the application does not use the iOS 26 theme. See the photo editor [theme guide](../projects/photo-editor/docs/theme.md) for color overrides and alternative dark-mode imports. + +### @rdlabo/ionic-angular-kit + +Version 22 changes the supported host framework range but does not rename or remove kit public APIs. After satisfying the compatibility requirements, existing kit imports and provider configuration remain valid. + +### @rdlabo/ionic-angular-scroll-header + +Version 22 has no package-specific API migration. Existing `rdlaboScrollHeader`, `rdlaboVirtualScrollHeader`, and `rdlaboFixVirtualScrollElement` usages remain valid after the dependency update. + +### @rdlabo/ngx-cdk-scroll-strategies + +Version 22 has no package-specific API migration. Existing dynamic-size virtual-scroll configuration remains valid after the dependency update. + +### Verification + +After applying the relevant package migrations, run the host application's normal checks and exercise the affected UI on each supported platform. For photo-editor, verify both editor and viewer modals against every toolbar color used by the application. diff --git a/e2e/photo-editor-ios26.spec.ts b/e2e/photo-editor-ios26.spec.ts new file mode 100644 index 0000000..65d7a90 --- /dev/null +++ b/e2e/photo-editor-ios26.spec.ts @@ -0,0 +1,59 @@ +import { readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { expect, test } from '@playwright/test'; + +const IOS26_CSS = 'node_modules/@rdlabo/ionic-theme-ios26/dist/css/ionic-theme-ios26.css'; +const IOS26_DARK_CLASS_CSS = 'node_modules/@rdlabo/ionic-theme-ios26/dist/css/ionic-theme-ios26-dark-class.css'; +const IONIC_DARK_CLASS_CSS = 'node_modules/@ionic/angular/css/palettes/dark.class.css'; +const requireFromWorkspace = createRequire(`${process.cwd()}/package.json`); +const PHOTO_EDITOR_IOS26_CSS = requireFromWorkspace.resolve('@rdlabo/ionic-angular-photo-editor/css/ios26-header-button-color-scheme.css'); + +test('exports a small adapter scoped to photo editor headers', () => { + const css = readFileSync(PHOTO_EDITOR_IOS26_CSS, 'utf8'); + const selectors = css.split('\n').filter((line) => line.endsWith(' {') && !line.trimStart().startsWith('@')); + + expect(Buffer.byteLength(css)).toBeLessThan(15_000); + expect(selectors.length).toBeGreaterThan(0); + expect(selectors.every((selector) => selector.includes('photo-editor-header-buttons-'))).toBe(true); +}); + +test.describe('Photo editor iOS 26 header button color scheme', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/main/photo-editor?ionic:mode=ios'); + await page.addStyleTag({ path: IOS26_CSS }); + await page.addStyleTag({ path: IONIC_DARK_CLASS_CSS }); + await page.addStyleTag({ path: IOS26_DARK_CLASS_CSS }); + await page.addStyleTag({ path: PHOTO_EDITOR_IOS26_CSS }); + }); + + test('forces light buttons inside an ambient dark palette', async ({ page }) => { + await page.locator('html').evaluate((element) => element.classList.add('ion-palette-dark')); + await page.getByText('Launch Photo Editor', { exact: true }).click(); + + const header = page.locator('ion-modal rdlabo-photo-editor ion-header'); + await header.evaluate((element) => { + element.classList.remove('photo-editor-header-buttons-dark'); + element.classList.add('photo-editor-header-buttons-light'); + (element as HTMLElement).style.colorScheme = 'light'; + }); + const button = header.locator('ion-buttons[slot="start"] ion-button'); + await button.evaluate((element) => element.classList.add('ion-activated')); + + await expect(header).toHaveCSS('--ios26-glass-background-rgb', '255, 255, 255'); + await expect(header).toHaveCSS('--ion-text-color-rgb', '0, 0, 0'); + await expect(button.locator('..')).toHaveCSS('backdrop-filter', 'blur(2px) saturate(3.6)'); + }); + + test('forces dark viewer buttons inside an ambient light palette', async ({ page }) => { + await page.locator('html').evaluate((element) => element.classList.remove('ion-palette-dark')); + await page.getByText('Launch Photo Viewer', { exact: true }).click(); + + const header = page.locator('ion-modal rdlabo-photo-viewer ion-header'); + const button = header.locator('ion-buttons[slot="start"] ion-button'); + await button.evaluate((element) => element.classList.add('ion-activated')); + + await expect(header).toHaveCSS('--ios26-glass-background-rgb', '62, 62, 62'); + await expect(header).toHaveCSS('--ion-text-color-rgb', '255, 255, 255'); + await expect(button.locator('..')).toHaveCSS('backdrop-filter', 'blur(7px) saturate(1.8)'); + }); +}); diff --git a/package-lock.json b/package-lock.json index 4a814c6..8e6f2ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "@playwright/test": "^1.57.0", "@rdlabo/capacitor-brotherprint": "^8.1.1", "@rdlabo/eslint-plugin-rules": "^21.2.6", + "@rdlabo/ionic-theme-ios26": "^3.0.0", "angular-eslint": "^22.1.0", "child_process": "^1.0.2", "dom-to-image-more": "^3.10.0", @@ -102,7 +103,8 @@ "peerDependencies": { "@angular/cdk": ">=21.0.0 <23.0.0", "@angular/common": ">=21.0.0 <23.0.0", - "@angular/core": ">=21.0.0 <23.0.0" + "@angular/core": ">=21.0.0 <23.0.0", + "@ionic/angular": "^9.0.0" } }, "dist/scroll-strategies": { @@ -7947,6 +7949,16 @@ "resolved": "dist/scroll-header", "link": true }, + "node_modules/@rdlabo/ionic-theme-ios26": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@rdlabo/ionic-theme-ios26/-/ionic-theme-ios26-3.0.0.tgz", + "integrity": "sha512-uZdPpAxuSpRqCKWpEgbqMp5MaCkPyJG/X3X/tozRjG1FIYL4S1rtnK0uSLZCeihze4WB/ssi3ZKWlTp8ljplUA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@ionic/core": ">=8.8.1 <10" + } + }, "node_modules/@rdlabo/ngx-cdk-scroll-strategies": { "resolved": "dist/scroll-strategies", "link": true diff --git a/package.json b/package.json index d1f2b3c..ddeed09 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "cap": "npm run build -- --configuration production && npx cap copy", "fmt": "prettier --parser typescript --write \"./**/*.ts\" && prettier --parser angular --write \"./**/*.html\"", "prebuild:kit": "ng build kit --configuration=production", - "prebuild:photo-editor": "ng build photo-editor --configuration=production", + "prebuild:photo-editor": "sass --no-source-map --load-path=node_modules projects/photo-editor/src/assets/ios26-header-button-color-scheme.scss projects/photo-editor/css/ios26-header-button-color-scheme.css && ng build photo-editor --configuration=production", "prebuild:scroll-header": "sass projects/scroll-header/src/assets/scroll-header.directive.scss projects/scroll-header/css/scroll-header.directive.css && ng build scroll-header --configuration=production", "prebuild:scroll-strategies": "ng build scroll-strategies --configuration=production", "prebuild": "npm run prebuild:kit && npm run prebuild:photo-editor && npm run prebuild:scroll-header && npm run prebuild:scroll-strategies", @@ -81,6 +81,7 @@ "@playwright/test": "^1.57.0", "@rdlabo/capacitor-brotherprint": "^8.1.1", "@rdlabo/eslint-plugin-rules": "^21.2.6", + "@rdlabo/ionic-theme-ios26": "^3.0.0", "angular-eslint": "^22.1.0", "child_process": "^1.0.2", "dom-to-image-more": "^3.10.0", diff --git a/projects/demo/src/app/photo-editor/demo-photo-editor-page.component.ts b/projects/demo/src/app/photo-editor/demo-photo-editor-page.component.ts index 8b04f13..d263d2c 100644 --- a/projects/demo/src/app/photo-editor/demo-photo-editor-page.component.ts +++ b/projects/demo/src/app/photo-editor/demo-photo-editor-page.component.ts @@ -71,6 +71,7 @@ export class DemoPhotoEditorPage { componentProps: { requireSquare: false, value: photoData, + headerButtonColorScheme: 'dark', }, }); await modal.present(); @@ -88,6 +89,7 @@ export class DemoPhotoEditorPage { index: 1, isCircle: false, enableDelete: true, + headerButtonColorScheme: 'dark', labels: { delete: 'Delete', }, diff --git a/projects/photo-editor/README.md b/projects/photo-editor/README.md index fbc398b..bcac481 100644 --- a/projects/photo-editor/README.md +++ b/projects/photo-editor/README.md @@ -8,12 +8,13 @@ This is a photo editor and viewer for modal page of Ionic Angular project using ### Choose by editing goal -| Goal | Guide | -| --- | --- | -| Load a photo from camera or album | [PhotoFileService](./docs/photo-file.md) | -| Crop and edit in a modal | [Photo Editor](./docs/editor.md) | -| Browse images in a modal | [Photo Viewer](./docs/viewer.md) | -| Override editor colors | [Theme](./docs/theme.md) | +| Goal | Guide | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| Load a photo from camera or album | [PhotoFileService](./docs/photo-file.md) | +| Crop and edit in a modal | [Photo Editor](./docs/editor.md) | +| Browse images in a modal | [Photo Viewer](./docs/viewer.md) | +| Override editor colors | [Theme](./docs/theme.md) | +| Upgrade from an earlier release | [Migration guide](https://github.com/rdlabo-dev/ionic-angular-library/blob/main/docs/migration.md#rdlaboionic-angular-photo-editor) | ## Quick start @@ -49,7 +50,6 @@ If you public your project to the web, you need to add the following input tag t ``` - ## Documentation Start with [Installation](#installation), then pick a guide. @@ -58,7 +58,10 @@ Start with [Installation](#installation), then pick a guide. - [Photo Editor](./docs/editor.md) — crop and edit in a modal. - [Photo Viewer](./docs/viewer.md) — browse images in a modal. - [Theme](./docs/theme.md) — CSS variables. +- [Migration guide](https://github.com/rdlabo-dev/ionic-angular-library/blob/main/docs/migration.md#rdlaboionic-angular-photo-editor) — breaking changes and required consumer updates. + **Full documentation:** [https://docs.rdlabo.dev/projects/ionic-angular-photo-editor](https://docs.rdlabo.dev/projects/ionic-angular-photo-editor) + diff --git a/projects/photo-editor/css/ios26-header-button-color-scheme.css b/projects/photo-editor/css/ios26-header-button-color-scheme.css new file mode 100644 index 0000000..f71b58d --- /dev/null +++ b/projects/photo-editor/css/ios26-header-button-color-scheme.css @@ -0,0 +1,114 @@ +ion-header.photo-editor-header-buttons-light { + --ion-text-color-rgb: 0, 0, 0; + --ios26-glass-background-rgb: 255, 255, 255; + --ios26-glass-box-shadow-color-rgb: 220, 220, 220; + --ios26-glass-border-color-rgb: 255, 255, 255; + --ios26-button-color-selected-rgb: 16, 16, 16; +} +ion-header.photo-editor-header-buttons-light ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear), ion-menu-button.menu-button-hidden)):has(.ion-activated) { + background: rgba(var(--ios26-glass-background-rgb), 0.72); + backdrop-filter: blur(2px) saturate(360%); + box-shadow: inset 0 0 8px 0 rgba(var(--ios26-glass-box-shadow-color-rgb), 0.2), 0 0 10px 0 rgba(var(--ios26-glass-box-shadow-color-rgb), 0.82); + border-top: 0.5px solid rgba(var(--ios26-glass-border-color-rgb), 1); + border-right: 0.5px solid rgba(var(--ios26-glass-border-color-rgb), 0.8); + border-bottom: 0.5px solid rgba(var(--ios26-glass-border-color-rgb), 1); + border-left: 0.5px solid rgba(var(--ios26-glass-border-color-rgb), 0.6); + transform: translateZ(0); + -webkit-transform: translateZ(0); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + box-shadow: inset 0 0 8px 0 rgba(var(--ios26-glass-box-shadow-color-rgb), 0.2), 0 0 8px -4px rgba(var(--ios26-glass-box-shadow-color-rgb), 0.92); +} +ion-header.photo-editor-header-buttons-light ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear), ion-menu-button.menu-button-hidden)) ion-button:not(.ios26-disabled).button-clear.ion-activated { + color: rgba(var(--ion-text-color-rgb), 0.1); + --color: rgba(var(--ion-text-color-rgb), 0.1); +} +ion-header.photo-editor-header-buttons-light ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear), ion-menu-button.menu-button-hidden)) ion-button:not(.ios26-disabled).button-clear.ion-activated.ion-color { + color: rgba(var(--ion-color-base-rgb, var(--ion-text-color-rgb)), 0.1); + --color: rgba(var(--ion-color-base-rgb, var(--ion-text-color-rgb)), 0.1); +} +ion-header.photo-editor-header-buttons-light ion-button.ios:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear) { + --color: rgba(var(--ion-text-color-rgb), 0.1); +} +ion-header.photo-editor-header-buttons-light ion-button.ios:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear).ion-color { + --color: rgba(var(--ion-color-base-rgb, var(--ion-text-color-rgb)), 0.1); +} +ion-header.photo-editor-header-buttons-light ion-button.ios:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear)::part(native) { + background: rgba(var(--ios26-glass-background-rgb), 0.72); + backdrop-filter: blur(2px) saturate(360%); + box-shadow: inset 0 0 8px 0 rgba(var(--ios26-glass-box-shadow-color-rgb), 0.2), 0 0 10px 0 rgba(var(--ios26-glass-box-shadow-color-rgb), 0.82); + border-top: 0.5px solid rgba(var(--ios26-glass-border-color-rgb), 1); + border-right: 0.5px solid rgba(var(--ios26-glass-border-color-rgb), 0.8); + border-bottom: 0.5px solid rgba(var(--ios26-glass-border-color-rgb), 1); + border-left: 0.5px solid rgba(var(--ios26-glass-border-color-rgb), 0.6); + transform: translateZ(0); + -webkit-transform: translateZ(0); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + box-shadow: inset 0 0 8px 0 rgba(var(--ios26-glass-box-shadow-color-rgb), 0.2), 0 0 8px -4px rgba(var(--ios26-glass-box-shadow-color-rgb), 0.92); +} +ion-header.photo-editor-header-buttons-light ion-button.ios:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear) ion-icon { + color: rgba(var(--ion-text-color-rgb), 0.1) !important; +} + +ion-header.photo-editor-header-buttons-dark { + --ion-text-color-rgb: 255, 255, 255; + --ios26-glass-background-rgb: 62, 62, 62; + --ios26-glass-box-shadow-color-rgb: 0, 0, 0 / 0; + --ios26-glass-border-color-rgb: 68, 68, 68; + --ios26-button-color-selected-rgb: 255, 255, 255; + --ios26-content-box-shadow-rgb: 0, 0, 0; +} +ion-header.photo-editor-header-buttons-dark ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear))):has(.ion-activated) { + background: rgba(var(--ios26-button-color-selected-rgb), 0.56); + backdrop-filter: blur(7px) saturate(180%); + box-shadow: inset 0 0 16px 0 rgba(var(--ios26-glass-box-shadow-color-rgb), 0.55); + border-top: 0.8px solid rgba(var(--ios26-button-color-selected-rgb), 0.8); + border-right: 0.8px solid rgba(var(--ios26-button-color-selected-rgb), 0.4); + border-bottom: 0.8px solid rgba(var(--ios26-button-color-selected-rgb), 0.8); + border-left: 0.8px solid rgba(var(--ios26-button-color-selected-rgb), 0.6); + transform: translateZ(0); + -webkit-transform: translateZ(0); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; +} +ion-header.photo-editor-header-buttons-dark ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear))) ion-button:not(.ios26-disabled).button-clear.ion-activated { + color: rgb(var(--ion-text-color-rgb, 0, 0, 0)); +} +ion-header.photo-editor-header-buttons-dark ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear))) ion-button:not(.ios26-disabled).button-clear.ion-activated.ion-color { + color: rgb(var(--ion-color-base-rgb, var(--ion-text-color-rgb, 0, 0, 0))); +} +ion-header.photo-editor-header-buttons-dark ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear))) ion-button:not(.ios26-disabled).ion-activated, +ion-header.photo-editor-header-buttons-dark ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear))) ion-back-button:not(.ios26-disabled).ion-activated { + --color: rgba(var(--ion-text-color-rgb, 0, 0, 0), 1); +} +ion-header.photo-editor-header-buttons-dark ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear))) ion-button:not(.ios26-disabled).ion-activated.ion-color, +ion-header.photo-editor-header-buttons-dark ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear))) ion-back-button:not(.ios26-disabled).ion-activated.ion-color { + --color: rgba(var(--ion-color-base-rgb, var(--ion-text-color-rgb, 0, 0, 0)), 1); +} +ion-header.photo-editor-header-buttons-dark ion-button:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear), +ion-header.photo-editor-header-buttons-dark ion-back-button:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear) { + --color: rgba(var(--ion-text-color-rgb, 0, 0, 0), 1); +} +ion-header.photo-editor-header-buttons-dark ion-button:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear).ion-color, +ion-header.photo-editor-header-buttons-dark ion-back-button:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear).ion-color { + --color: rgba(var(--ion-color-base-rgb, var(--ion-text-color-rgb, 0, 0, 0)), 1); +} +ion-header.photo-editor-header-buttons-dark ion-button:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear)::part(native), +ion-header.photo-editor-header-buttons-dark ion-back-button:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear)::part(native) { + background: rgba(var(--ios26-button-color-selected-rgb), 0.56); + backdrop-filter: blur(7px) saturate(180%); + box-shadow: inset 0 0 16px 0 rgba(var(--ios26-glass-box-shadow-color-rgb), 0.55); + border-top: 0.8px solid rgba(var(--ios26-button-color-selected-rgb), 0.8); + border-right: 0.8px solid rgba(var(--ios26-button-color-selected-rgb), 0.4); + border-bottom: 0.8px solid rgba(var(--ios26-button-color-selected-rgb), 0.8); + border-left: 0.8px solid rgba(var(--ios26-button-color-selected-rgb), 0.6); + transform: translateZ(0); + -webkit-transform: translateZ(0); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; +} +ion-header.photo-editor-header-buttons-dark ion-button:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear) ion-icon, +ion-header.photo-editor-header-buttons-dark ion-back-button:not(.ios26-disabled).ion-activated:not(.button-solid):not(.button-outline):not(.button-clear) ion-icon { + color: rgb(255, 255, 255) !important; +} diff --git a/projects/photo-editor/docs/editor.md b/projects/photo-editor/docs/editor.md index 8f8e198..5b48faa 100644 --- a/projects/photo-editor/docs/editor.md +++ b/projects/photo-editor/docs/editor.md @@ -1,18 +1,20 @@ Present `PhotoEditorPage` in an Ionic modal. Call this after [Installation](../README.md#installation). ```typescript -import { PhotoEditorPage, IPhotoEditorDismiss } from '@rdlabo/ionic-angular-photo-editor'; +import { PhotoEditorPage, IPhotoEditorDismiss, PhotoEditorProps } from '@rdlabo/ionic-angular-photo-editor'; (async () => { + const componentProps = { + requireSquare: false, + value: 'https://picsum.photos/200/300', + headerButtonColorScheme: 'dark', + labels: { + save: '送信', // change '保存' to '送信' + }, + } satisfies PhotoEditorProps; const modal = await this.modalCtrl.create({ component: PhotoEditorPage, - componentProps: { - requireSquare: false, - value: 'https://picsum.photos/200/300', - labels: { - save: '送信', // change '保存' to '送信' - }, - }, + componentProps, }); await modal.present(); const { data } = await modal.onWillDismiss(); @@ -37,3 +39,7 @@ The image url or base64 string. If set, the label is overwritten. List is [here](https://github.com/rdlabo-dev/ionic-angular-library/blob/v21.6.2/projects/photo-editor/src/lib/dictionaries.ts). + +#### headerButtonColorScheme: 'light' | 'dark' + +Required. Select `dark` for a dark/black `ion-toolbar` and `light` for a light/white toolbar. The library cannot infer the toolbar appearance from CSS, translucent content, or runtime theme overrides. diff --git a/projects/photo-editor/docs/theme.md b/projects/photo-editor/docs/theme.md index 177128f..34220b6 100644 --- a/projects/photo-editor/docs/theme.md +++ b/projects/photo-editor/docs/theme.md @@ -15,5 +15,23 @@ Default color is set, but user can overwrite it: https://github.com/rdlabo-dev/i --ion-photo-editor-primary: #4d8dff; --ion-photo-editor-danger: #f24c58; --ion-photo-editor-success: #2dd55b; + + --ion-photo-editor-header-button-color-on-light: #222428; + --ion-photo-editor-header-button-color-on-dark: #f4f5f8; } ``` + +## Header button color scheme + +`PhotoEditorPage` and `PhotoViewerPage` require `headerButtonColorScheme: 'light' | 'dark'` in their modal `componentProps`. Select `dark` for a dark/black `ion-toolbar` and `light` for a light/white toolbar. The consumer must choose because the library cannot reliably infer the final toolbar appearance from CSS, translucency, or runtime theme overrides. + +For `@rdlabo/ionic-theme-ios26` v3, import the optional integration stylesheet after the iOS 26 theme and dark-mode styles: + +```scss +@import '@rdlabo/ionic-theme-ios26/dist/css/ionic-theme-ios26.css'; +@import '@ionic/angular/css/palettes/dark.class.css'; +@import '@rdlabo/ionic-theme-ios26/dist/css/ionic-theme-ios26-dark-class.css'; +@import '@rdlabo/ionic-angular-photo-editor/css/ios26-header-button-color-scheme.css'; +``` + +Use the matching Always or System dark-mode import instead when appropriate. The photo-editor integration stylesheet must remain last so its local header scheme can override the ambient application scheme. Applications that do not use the iOS 26 theme should not import this optional stylesheet; they receive only the regular Ionic button foreground-color switch. diff --git a/projects/photo-editor/docs/viewer.md b/projects/photo-editor/docs/viewer.md index 7abe5b0..1d1e8cf 100644 --- a/projects/photo-editor/docs/viewer.md +++ b/projects/photo-editor/docs/viewer.md @@ -1,16 +1,18 @@ Present `PhotoViewerPage` in an Ionic modal. Call this after [Installation](../README.md#installation). ```typescript -import { PhotoViewerPage, IPhotoViewerDismiss } from '@rdlabo/ionic-angular-photo-editor'; +import { PhotoViewerPage, IPhotoViewerDismiss, PhotoViewerProps } from '@rdlabo/ionic-angular-photo-editor'; (async () => { + const componentProps = { + imageUrls: ['https://picsum.photos/200/300', 'https://picsum.photos/200/300'], + index: 0, + isCircle: false, + headerButtonColorScheme: 'dark', + } satisfies PhotoViewerProps; const modal = await this.modalCtrl.create({ component: PhotoViewerPage, - componentProps: { - imageUrls: ['https://picsum.photos/200/300', 'https://picsum.photos/200/300'], - index: 0, - isCircle: false, - }, + componentProps, }); await modal.present(); const { data } = await modal.onWillDismiss(); @@ -47,3 +49,7 @@ If true, enable footer safe area for iOS. If set, the label is overwritten. List is [here](https://github.com/rdlabo-dev/ionic-angular-library/blob/v21.6.2/projects/photo-editor/src/lib/dictionaries.ts). + +#### headerButtonColorScheme: 'light' | 'dark' + +Required. Select `dark` for a dark/black `ion-toolbar` and `light` for a light/white toolbar. The library cannot infer the toolbar appearance from CSS, translucent content, or runtime theme overrides. diff --git a/projects/photo-editor/ng-package.json b/projects/photo-editor/ng-package.json index ecc9c0d..b3a4256 100644 --- a/projects/photo-editor/ng-package.json +++ b/projects/photo-editor/ng-package.json @@ -4,7 +4,5 @@ "lib": { "entryFile": "src/public-api.ts" }, - "assets": [ - "docs" - ] + "assets": ["docs", "css"] } diff --git a/projects/photo-editor/package.json b/projects/photo-editor/package.json index 73c95ee..0dd3a52 100644 --- a/projects/photo-editor/package.json +++ b/projects/photo-editor/package.json @@ -18,5 +18,11 @@ "dependencies": { "tslib": "^2.3.0" }, + "exports": { + "./css/*": { + "style": "./css/*", + "default": "./css/*" + } + }, "sideEffects": false } diff --git a/projects/photo-editor/src/assets/ios26-header-button-color-scheme.scss b/projects/photo-editor/src/assets/ios26-header-button-color-scheme.scss new file mode 100644 index 0000000..3f8897e --- /dev/null +++ b/projects/photo-editor/src/assets/ios26-header-button-color-scheme.scss @@ -0,0 +1,52 @@ +@use '@rdlabo/ionic-theme-ios26/src/styles/utils/api' as ios26-api; +@use '@rdlabo/ionic-theme-ios26/src/styles/utils/theme-dark' as ios26-dark; + +ion-header.photo-editor-header-buttons-light { + --ion-text-color-rgb: 0, 0, 0; + --ios26-glass-background-rgb: 255, 255, 255; + --ios26-glass-box-shadow-color-rgb: 220, 220, 220; + --ios26-glass-border-color-rgb: 255, 255, 255; + --ios26-button-color-selected-rgb: 16, 16, 16; + + ion-buttons.ios:not(.ios26-disabled):not(:has(ion-back-button, ion-button:not(.button-clear), ion-menu-button.menu-button-hidden)) { + &:has(.ion-activated) { + @include ios26-api.glass-background; + @include ios26-api.glass-background-button-activated; + } + + ion-button:not(.ios26-disabled).button-clear.ion-activated { + color: rgba(var(--ion-text-color-rgb), 0.1); + --color: rgba(var(--ion-text-color-rgb), 0.1); + + &.ion-color { + color: rgba(var(--ion-color-base-rgb, var(--ion-text-color-rgb)), 0.1); + --color: rgba(var(--ion-color-base-rgb, var(--ion-text-color-rgb)), 0.1); + } + } + } + + ion-button.ios:not(.ios26-disabled) { + &.ion-activated:not(.button-solid):not(.button-outline):not(.button-clear) { + --color: rgba(var(--ion-text-color-rgb), 0.1); + + &.ion-color { + --color: rgba(var(--ion-color-base-rgb, var(--ion-text-color-rgb)), 0.1); + } + + &::part(native) { + @include ios26-api.glass-background; + @include ios26-api.glass-background-button-activated; + } + + ion-icon { + color: rgba(var(--ion-text-color-rgb), 0.1) !important; + } + } + } +} + +ion-header.photo-editor-header-buttons-dark { + --ion-text-color-rgb: 255, 255, 255; + @include ios26-dark.default-variables; + @include ios26-dark.ion-button; +} diff --git a/projects/photo-editor/src/lib/pages/core.scss b/projects/photo-editor/src/lib/pages/core.scss index 03a39c1..657f7d6 100644 --- a/projects/photo-editor/src/lib/pages/core.scss +++ b/projects/photo-editor/src/lib/pages/core.scss @@ -27,6 +27,14 @@ //--ion-color-photo-editor-success-tint: var(--ion-photo-editor-success, #42d96b); } +ion-header.photo-editor-header-buttons-light ion-button:not(.ion-color) { + --color: var(--ion-photo-editor-header-button-color-on-light, #222428); +} + +ion-header.photo-editor-header-buttons-dark ion-button:not(.ion-color) { + --color: var(--ion-photo-editor-header-button-color-on-dark, #f4f5f8); +} + .ion-color-photo-editor-primary { --ion-color-base: var(--ion-color-photo-editor-primary); //--ion-color-base-rgb: var(--ion-color-photo-editor-primary-rgb); diff --git a/projects/photo-editor/src/lib/pages/photo-editor/photo-editor.page.html b/projects/photo-editor/src/lib/pages/photo-editor/photo-editor.page.html index 8fdaf8f..31aa6e6 100644 --- a/projects/photo-editor/src/lib/pages/photo-editor/photo-editor.page.html +++ b/projects/photo-editor/src/lib/pages/photo-editor/photo-editor.page.html @@ -1,4 +1,8 @@ - + { let component: PhotoEditorPage; let fixture: ComponentFixture; + let componentRef: ComponentRef; beforeEach(() => { TestBed.configureTestingModule({ @@ -12,10 +14,39 @@ describe('PhotoEditorPage', () => { }); fixture = TestBed.createComponent(PhotoEditorPage); component = fixture.componentInstance; + componentRef = fixture.componentRef; + componentRef.setInput('value', 'data:image/png;base64,'); + componentRef.setInput('headerButtonColorScheme', 'dark'); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('applies the configured dark color scheme to the header', () => { + const header = fixture.nativeElement.querySelector('ion-header'); + const closeButton = header.querySelector('ion-button'); + + expect(header.classList.contains('photo-editor-header-buttons-dark')).toBe(true); + expect(header.classList.contains('photo-editor-header-buttons-light')).toBe(false); + expect(header.style.colorScheme).toBe('dark'); + expect(getComputedStyle(closeButton).getPropertyValue('--color').trim()).toBe( + 'var(--ion-photo-editor-header-button-color-on-dark, #f4f5f8)', + ); + }); + + it('applies the configured light color scheme to regular Ionic buttons', () => { + componentRef.setInput('headerButtonColorScheme', 'light'); + fixture.detectChanges(); + const header = fixture.nativeElement.querySelector('ion-header'); + const closeButton = header.querySelector('ion-button'); + + expect(header.classList.contains('photo-editor-header-buttons-dark')).toBe(false); + expect(header.classList.contains('photo-editor-header-buttons-light')).toBe(true); + expect(header.style.colorScheme).toBe('light'); + expect(getComputedStyle(closeButton).getPropertyValue('--color').trim()).toBe( + 'var(--ion-photo-editor-header-button-color-on-light, #222428)', + ); + }); }); diff --git a/projects/photo-editor/src/lib/pages/photo-editor/photo-editor.page.ts b/projects/photo-editor/src/lib/pages/photo-editor/photo-editor.page.ts index 7ff8868..9785d98 100644 --- a/projects/photo-editor/src/lib/pages/photo-editor/photo-editor.page.ts +++ b/projects/photo-editor/src/lib/pages/photo-editor/photo-editor.page.ts @@ -19,13 +19,13 @@ import ImageEditor from 'tui-image-editor'; import { filterPreset } from '../../filter-preset'; import { Subscription } from 'rxjs'; import { toObservable } from '@angular/core/rxjs-interop'; -import { IDictionaryForEditor, IFilter, IPhotoEditorDismiss, ISize } from '../../types'; +import { IDictionaryForEditor, IFilter, IPhotoEditorDismiss, ISize, PhotoEditorHeaderButtonColorScheme } from '../../types'; import { ionComponents } from '../../ion-components'; import { dictionaryForEditor } from '../../dictionaries'; import { initializeEditorIcons, waitToFindDom } from '../util'; @Component({ - selector: 'app-editor-image', + selector: 'rdlabo-photo-editor', templateUrl: './photo-editor.page.html', styleUrls: ['../core.scss', './photo-editor.page.scss'], imports: [CommonModule, FormsModule, ...ionComponents], @@ -40,6 +40,7 @@ export class PhotoEditorPage implements OnInit, OnDestroy, ViewDidEnter, ViewDid transform: coerceBooleanProperty, }); readonly labels = input | undefined>(undefined); + readonly headerButtonColorScheme = input.required(); readonly setLabels = effect(() => { if (this.labels()) { this.dictionary.update((value) => ({ ...value, ...this.labels() })); diff --git a/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.html b/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.html index 538aa7b..4744811 100644 --- a/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.html +++ b/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.html @@ -1,7 +1,12 @@ - + - + diff --git a/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.scss b/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.scss index e18e5e9..e0d0073 100644 --- a/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.scss +++ b/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.scss @@ -1,7 +1,3 @@ -ion-button.viewer-close-button { - --color: var(--editor-color) !important; -} - img.circle { border-radius: 50%; display: block; diff --git a/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.spec.ts b/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.spec.ts index 1d36879..77960c7 100644 --- a/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.spec.ts +++ b/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.spec.ts @@ -16,10 +16,37 @@ describe('PhotoViewerPage', () => { component = fixture.componentInstance; componentRef = fixture.componentRef; componentRef.setInput('imageUrls', []); + componentRef.setInput('headerButtonColorScheme', 'dark'); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('applies the configured dark color scheme to the header', () => { + const header = fixture.nativeElement.querySelector('ion-header'); + const closeButton = header.querySelector('ion-button'); + + expect(header.classList.contains('photo-editor-header-buttons-dark')).toBe(true); + expect(header.classList.contains('photo-editor-header-buttons-light')).toBe(false); + expect(header.style.colorScheme).toBe('dark'); + expect(getComputedStyle(closeButton).getPropertyValue('--color').trim()).toBe( + 'var(--ion-photo-editor-header-button-color-on-dark, #f4f5f8)', + ); + }); + + it('applies the configured light color scheme to regular Ionic buttons', () => { + componentRef.setInput('headerButtonColorScheme', 'light'); + fixture.detectChanges(); + const header = fixture.nativeElement.querySelector('ion-header'); + const closeButton = header.querySelector('ion-button'); + + expect(header.classList.contains('photo-editor-header-buttons-dark')).toBe(false); + expect(header.classList.contains('photo-editor-header-buttons-light')).toBe(true); + expect(header.style.colorScheme).toBe('light'); + expect(getComputedStyle(closeButton).getPropertyValue('--color').trim()).toBe( + 'var(--ion-photo-editor-header-button-color-on-light, #222428)', + ); + }); }); diff --git a/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.ts b/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.ts index 574851d..9a176b6 100644 --- a/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.ts +++ b/projects/photo-editor/src/lib/pages/photo-viewer/photo-viewer.page.ts @@ -16,14 +16,14 @@ import { Navigation, Zoom } from 'swiper/modules'; import { fromEvent, Subscription, throttleTime, withLatestFrom, zipWith } from 'rxjs'; import { SwiperContainer } from 'swiper/element'; import { ionComponents } from '../../ion-components'; -import { IDictionaryForViewer, IPhotoViewerDismiss } from '../../types'; +import { IDictionaryForViewer, IPhotoViewerDismiss, PhotoEditorHeaderButtonColorScheme } from '../../types'; import { register } from 'swiper/element/bundle'; import { dictionaryForViewer } from '../../dictionaries'; import { BooleanInput, coerceBooleanProperty, coerceNumberProperty, NumberInput } from '@angular/cdk/coercion'; import { initializeViewerIcons, waitToFindDom } from '../util'; @Component({ - selector: 'app-photo-image', + selector: 'rdlabo-photo-viewer', templateUrl: './photo-viewer.page.html', styleUrls: ['../core.scss', './photo-viewer.page.scss'], imports: [...ionComponents], @@ -45,6 +45,7 @@ export class PhotoViewerPage implements OnInit, OnDestroy { transform: coerceBooleanProperty, }); readonly labels = input>(); + readonly headerButtonColorScheme = input.required(); readonly setLabels = effect(() => { if (this.labels()) { this.dictionary.update((value) => ({ ...value, ...this.labels() })); diff --git a/projects/photo-editor/src/lib/types.ts b/projects/photo-editor/src/lib/types.ts index ad549c3..677cc83 100644 --- a/projects/photo-editor/src/lib/types.ts +++ b/projects/photo-editor/src/lib/types.ts @@ -54,14 +54,18 @@ export interface IDictionaryForService { cancel: string; } +/** Color scheme used by photo editor and viewer header buttons. */ +export type PhotoEditorHeaderButtonColorScheme = 'light' | 'dark'; + /** Props for presenting {@link PhotoViewerPage} via Ionic Modal `componentProps`. */ export interface PhotoViewerProps { - imageUrls?: string[]; + imageUrls: string[]; index?: number; isCircle?: boolean; enableDelete?: boolean; enableFooterSafeArea?: boolean; labels?: Partial; + headerButtonColorScheme: PhotoEditorHeaderButtonColorScheme; } /** Props for presenting {@link PhotoEditorPage} via Ionic Modal `componentProps`. */ @@ -69,4 +73,5 @@ export interface PhotoEditorProps { requireSquare?: boolean; value: string; labels?: Partial; + headerButtonColorScheme: PhotoEditorHeaderButtonColorScheme; } diff --git a/projects/scroll-header/package.json b/projects/scroll-header/package.json index 8ee0b40..60fb576 100644 --- a/projects/scroll-header/package.json +++ b/projects/scroll-header/package.json @@ -8,7 +8,8 @@ "peerDependencies": { "@angular/cdk": ">=21.0.0 <23.0.0", "@angular/common": ">=21.0.0 <23.0.0", - "@angular/core": ">=21.0.0 <23.0.0" + "@angular/core": ">=21.0.0 <23.0.0", + "@ionic/angular": "^9.0.0" }, "dependencies": { "tslib": "^2.3.0"