capacitor-extended-dialog 0.0.14 → 0.0.15

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.
@@ -13,5 +13,6 @@ Pod::Spec.new do |s|
13
13
  s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
14
  s.ios.deployment_target = '15.0'
15
15
  s.dependency 'Capacitor'
16
+ s.dependency 'SVGKit', '~> 3.0'
16
17
  s.swift_version = '5.1'
17
18
  end
package/Package.swift CHANGED
@@ -10,14 +10,16 @@ let package = Package(
10
10
  targets: ["ExtendedDialogPlugin"])
11
11
  ],
12
12
  dependencies: [
13
- .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
14
+ .package(url: "https://github.com/SVGKit/SVGKit.git", from: "3.0.0")
14
15
  ],
15
16
  targets: [
16
17
  .target(
17
18
  name: "ExtendedDialogPlugin",
18
19
  dependencies: [
19
20
  .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
- .product(name: "Cordova", package: "capacitor-swift-pm")
21
+ .product(name: "Cordova", package: "capacitor-swift-pm"),
22
+ .product(name: "SVGKit", package: "SVGKit")
21
23
  ],
22
24
  path: "ios/Sources/ExtendedDialogPlugin"),
23
25
  .testTarget(
package/README.md CHANGED
@@ -249,24 +249,24 @@ Show a message sheet dialog with an optional header logo, title, and message.
249
249
 
250
250
  #### SheetOptions
251
251
 
252
- | Prop | Type | Description |
253
- | ------------------------ | ------------------------------------------------- | -------------------------------------------------------- |
254
- | **`headerLogo`** | <code>string</code> | Header logo - supports base64 data URL or HTTP/HTTPS URL |
255
- | **`title`** | <code>string</code> | Sheet title |
256
- | **`subtitle`** | <code>string</code> | Optional subtitle displayed between title and rows |
257
- | **`rows`** | <code>SheetRow[]</code> | Description rows |
258
- | **`confirmButtonTitle`** | <code>string</code> | Confirm button title |
259
- | **`cancelButtonTitle`** | <code>string</code> | Cancel button title |
260
- | **`mode`** | <code><a href="#dialogmode">DialogMode</a></code> | Dialog mode |
252
+ | Prop | Type | Description |
253
+ | ------------------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
254
+ | **`headerLogo`** | <code>string</code> | Header logo. Supports: - PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL - SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,&lt;svg ...&gt;...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg` |
255
+ | **`title`** | <code>string</code> | Sheet title |
256
+ | **`subtitle`** | <code>string</code> | Optional subtitle displayed between title and rows |
257
+ | **`rows`** | <code>SheetRow[]</code> | Description rows |
258
+ | **`confirmButtonTitle`** | <code>string</code> | Confirm button title |
259
+ | **`cancelButtonTitle`** | <code>string</code> | Cancel button title |
260
+ | **`mode`** | <code><a href="#dialogmode">DialogMode</a></code> | Dialog mode |
261
261
 
262
262
 
263
263
  #### SheetRow
264
264
 
265
- | Prop | Type | Description |
266
- | ----------- | ------------------- | --------------------------------------------------------------- |
267
- | **`title`** | <code>string</code> | Row title (required) |
268
- | **`logo`** | <code>string</code> | Optional logo/icon - supports base64 data URL or HTTP/HTTPS URL |
269
- | **`value`** | <code>string</code> | Optional value displayed on the right |
265
+ | Prop | Type | Description |
266
+ | ----------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
267
+ | **`title`** | <code>string</code> | Row title (required) |
268
+ | **`logo`** | <code>string</code> | Optional logo/icon. Supports: - PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL - SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,&lt;svg ...&gt;...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg` |
269
+ | **`value`** | <code>string</code> | Optional value displayed on the right |
270
270
 
271
271
 
272
272
  #### MessageSheetResult
@@ -278,15 +278,15 @@ Show a message sheet dialog with an optional header logo, title, and message.
278
278
 
279
279
  #### MessageSheetOptions
280
280
 
281
- | Prop | Type | Description |
282
- | ------------------------ | ------------------------------------------------- | -------------------------------------------------------- |
283
- | **`headerLogo`** | <code>string</code> | Header logo - supports base64 data URL or HTTP/HTTPS URL |
284
- | **`title`** | <code>string</code> | Sheet title |
285
- | **`subtitle`** | <code>string</code> | Optional subtitle displayed between title and message |
286
- | **`message`** | <code>string</code> | Message body displayed in the sheet |
287
- | **`confirmButtonTitle`** | <code>string</code> | Confirm button title |
288
- | **`cancelButtonTitle`** | <code>string</code> | Cancel button title |
289
- | **`mode`** | <code><a href="#dialogmode">DialogMode</a></code> | Dialog mode |
281
+ | Prop | Type | Description |
282
+ | ------------------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
283
+ | **`headerLogo`** | <code>string</code> | Header logo. Supports: - PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL - SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,&lt;svg ...&gt;...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg` |
284
+ | **`title`** | <code>string</code> | Sheet title |
285
+ | **`subtitle`** | <code>string</code> | Optional subtitle displayed between title and message |
286
+ | **`message`** | <code>string</code> | Message body displayed in the sheet |
287
+ | **`confirmButtonTitle`** | <code>string</code> | Confirm button title |
288
+ | **`cancelButtonTitle`** | <code>string</code> | Cancel button title |
289
+ | **`mode`** | <code><a href="#dialogmode">DialogMode</a></code> | Dialog mode |
290
290
 
291
291
 
292
292
  ### Type Aliases
@@ -53,6 +53,7 @@ dependencies {
53
53
  implementation project(':capacitor-android')
54
54
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55
55
  implementation 'com.google.android.material:material:1.12.0'
56
+ implementation 'com.caverock:androidsvg-aar:1.4'
56
57
  testImplementation "junit:junit:$junitVersion"
57
58
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
58
59
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
@@ -7,7 +7,6 @@ import android.content.res.ColorStateList;
7
7
  import android.graphics.Bitmap;
8
8
  import android.graphics.BitmapFactory;
9
9
  import android.os.Bundle;
10
- import android.util.Base64;
11
10
  import android.util.TypedValue;
12
11
  import android.view.ContextThemeWrapper;
13
12
  import android.view.Gravity;
@@ -52,6 +51,8 @@ public class SheetBottomDialogFragment extends BottomSheetDialogFragment {
52
51
  private static final String ARG_SUBTITLE = "subtitle";
53
52
  private static final String ARG_IS_MESSAGE_SHEET = "isMessageSheet";
54
53
 
54
+ private static final Executor IMAGE_EXECUTOR = Executors.newCachedThreadPool();
55
+
55
56
  private ExtendedDialog.SheetCallback sheetCallback;
56
57
  private boolean dismissed = false;
57
58
  private Context themedContext;
@@ -706,28 +707,32 @@ public class SheetBottomDialogFragment extends BottomSheetDialogFragment {
706
707
  }
707
708
 
708
709
  private void loadImageAsync(ImageView imageView, String imageUrl) {
709
- Executor executor = Executors.newSingleThreadExecutor();
710
- executor.execute(() -> {
710
+ if (imageUrl == null || imageUrl.isEmpty()) return;
711
+ IMAGE_EXECUTOR.execute(() -> {
711
712
  try {
712
- Bitmap bitmap;
713
- if (imageUrl.startsWith("data:")) {
714
- String base64Data = imageUrl.substring(imageUrl.indexOf(",") + 1);
715
- byte[] decodedBytes = Base64.decode(base64Data, Base64.DEFAULT);
716
- bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
717
- } else {
718
- InputStream inputStream = new URL(imageUrl).openStream();
719
- bitmap = BitmapFactory.decodeStream(inputStream);
720
- inputStream.close();
721
- }
713
+ Bitmap bitmap = decodeImage(imageUrl, imageView);
722
714
  if (bitmap != null) {
723
715
  imageView.post(() -> imageView.setImageBitmap(bitmap));
724
716
  }
725
717
  } catch (Exception e) {
726
- // Silently fail - image won't be displayed
718
+ android.util.Log.w("ExtendedDialog", "Failed to load image: " + e.getMessage());
727
719
  }
728
720
  });
729
721
  }
730
722
 
723
+ private static Bitmap decodeImage(String imageUrl, ImageView imageView) throws Exception {
724
+ if (SvgImageLoader.isSvgSource(imageUrl)) {
725
+ return SvgImageLoader.render(imageUrl, imageView);
726
+ }
727
+ if (SvgImageLoader.isDataUrl(imageUrl)) {
728
+ byte[] decodedBytes = SvgImageLoader.decodeDataUrl(imageUrl);
729
+ return BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
730
+ }
731
+ try (InputStream inputStream = new URL(imageUrl).openStream()) {
732
+ return BitmapFactory.decodeStream(inputStream);
733
+ }
734
+ }
735
+
731
736
  private void handleConfirm() {
732
737
  if (dismissed) return;
733
738
  dismissed = true;
@@ -0,0 +1,90 @@
1
+ package com.albermonte.extendeddialog;
2
+
3
+ import android.graphics.Bitmap;
4
+ import android.graphics.Canvas;
5
+ import android.net.Uri;
6
+ import android.util.Base64;
7
+ import android.view.ViewGroup;
8
+ import android.widget.ImageView;
9
+ import com.caverock.androidsvg.SVG;
10
+ import com.caverock.androidsvg.SVGParseException;
11
+ import java.io.ByteArrayInputStream;
12
+ import java.io.IOException;
13
+ import java.io.InputStream;
14
+ import java.net.URL;
15
+ import java.net.URLDecoder;
16
+ import java.nio.charset.StandardCharsets;
17
+ import java.util.Locale;
18
+
19
+ final class SvgImageLoader {
20
+
21
+ private static final String SVG_DATA_URL_PREFIX = "data:image/svg+xml";
22
+ private static final String DATA_URL_PREFIX = "data:";
23
+ private static final String SVG_SUFFIX = ".svg";
24
+
25
+ private SvgImageLoader() {}
26
+
27
+ static boolean isDataUrl(String source) {
28
+ return source != null && source.regionMatches(true, 0, DATA_URL_PREFIX, 0, DATA_URL_PREFIX.length());
29
+ }
30
+
31
+ static boolean isSvgSource(String source) {
32
+ if (source == null) return false;
33
+ if (source.regionMatches(true, 0, SVG_DATA_URL_PREFIX, 0, SVG_DATA_URL_PREFIX.length())) return true;
34
+ Uri uri = Uri.parse(source);
35
+ String scheme = uri.getScheme();
36
+ if (scheme == null || (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https"))) {
37
+ return false;
38
+ }
39
+ String path = uri.getPath();
40
+ return path != null && path.toLowerCase(Locale.ROOT).endsWith(SVG_SUFFIX);
41
+ }
42
+
43
+ /** Supports `;base64` and percent-encoded payloads. */
44
+ static byte[] decodeDataUrl(String source) throws IOException {
45
+ int comma = source.indexOf(',');
46
+ if (comma < 0) throw new IOException("Malformed data URL");
47
+ String header = source.substring(0, comma);
48
+ String payload = source.substring(comma + 1);
49
+ if (header.toLowerCase(Locale.ROOT).contains(";base64")) {
50
+ return Base64.decode(payload, Base64.DEFAULT);
51
+ }
52
+ return URLDecoder.decode(payload, StandardCharsets.UTF_8.name()).getBytes(StandardCharsets.UTF_8);
53
+ }
54
+
55
+ /** Sizes Bitmap to {@code targetView}'s layout params (or 64dp default). */
56
+ static Bitmap render(String source, ImageView targetView) throws SVGParseException, IOException {
57
+ SVG svg = parse(source);
58
+ ViewGroup.LayoutParams params = targetView.getLayoutParams();
59
+ int width = params != null ? params.width : 0;
60
+ int height = params != null ? params.height : 0;
61
+ if (width <= 0 || height <= 0) {
62
+ float density = targetView.getResources().getDisplayMetrics().density;
63
+ int fallback = (int) (64 * density);
64
+ if (width <= 0) width = fallback;
65
+ if (height <= 0) height = fallback;
66
+ }
67
+
68
+ // Rescale via viewBox + preserveAspectRatio so small-viewport SVGs
69
+ // (e.g. width="20") aren't rendered tiny in the top-left corner.
70
+ svg.setDocumentWidth(width);
71
+ svg.setDocumentHeight(height);
72
+
73
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
74
+ Canvas canvas = new Canvas(bitmap);
75
+ svg.renderToCanvas(canvas);
76
+ return bitmap;
77
+ }
78
+
79
+ private static SVG parse(String source) throws SVGParseException, IOException {
80
+ if (isDataUrl(source)) {
81
+ byte[] bytes = decodeDataUrl(source);
82
+ try (InputStream in = new ByteArrayInputStream(bytes)) {
83
+ return SVG.getFromInputStream(in);
84
+ }
85
+ }
86
+ try (InputStream in = new URL(source).openStream()) {
87
+ return SVG.getFromInputStream(in);
88
+ }
89
+ }
90
+ }
package/dist/docs.json CHANGED
@@ -545,7 +545,7 @@
545
545
  {
546
546
  "name": "headerLogo",
547
547
  "tags": [],
548
- "docs": "Header logo - supports base64 data URL or HTTP/HTTPS URL",
548
+ "docs": "Header logo. Supports:\n- PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL\n- SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,<svg ...>...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg`",
549
549
  "complexTypes": [],
550
550
  "type": "string | undefined"
551
551
  },
@@ -614,7 +614,7 @@
614
614
  {
615
615
  "name": "logo",
616
616
  "tags": [],
617
- "docs": "Optional logo/icon - supports base64 data URL or HTTP/HTTPS URL",
617
+ "docs": "Optional logo/icon. Supports:\n- PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL\n- SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,<svg ...>...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg`",
618
618
  "complexTypes": [],
619
619
  "type": "string | undefined"
620
620
  },
@@ -653,7 +653,7 @@
653
653
  {
654
654
  "name": "headerLogo",
655
655
  "tags": [],
656
- "docs": "Header logo - supports base64 data URL or HTTP/HTTPS URL",
656
+ "docs": "Header logo. Supports:\n- PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL\n- SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,<svg ...>...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg`",
657
657
  "complexTypes": [],
658
658
  "type": "string | undefined"
659
659
  },
@@ -260,13 +260,21 @@ export interface MultiSelectResult {
260
260
  export interface SheetRow {
261
261
  /** Row title (required) */
262
262
  title: string;
263
- /** Optional logo/icon - supports base64 data URL or HTTP/HTTPS URL */
263
+ /**
264
+ * Optional logo/icon. Supports:
265
+ * - PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL
266
+ * - SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,<svg ...>...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg`
267
+ */
264
268
  logo?: string;
265
269
  /** Optional value displayed on the right */
266
270
  value?: string;
267
271
  }
268
272
  export interface SheetOptions extends DialogStyleOptions {
269
- /** Header logo - supports base64 data URL or HTTP/HTTPS URL */
273
+ /**
274
+ * Header logo. Supports:
275
+ * - PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL
276
+ * - SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,<svg ...>...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg`
277
+ */
270
278
  headerLogo?: string;
271
279
  /** Sheet title */
272
280
  title: string;
@@ -282,7 +290,11 @@ export interface SheetOptions extends DialogStyleOptions {
282
290
  mode?: DialogMode;
283
291
  }
284
292
  export interface MessageSheetOptions extends DialogStyleOptions {
285
- /** Header logo - supports base64 data URL or HTTP/HTTPS URL */
293
+ /**
294
+ * Header logo. Supports:
295
+ * - PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL
296
+ * - SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,<svg ...>...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg`
297
+ */
286
298
  headerLogo?: string;
287
299
  /** Sheet title */
288
300
  title: string;
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Dialog presentation mode.\n *\n * - `'basic'` — Standard modal dialog (default)\n * - `'fullscreen'` — Full-screen dialog presentation\n */\nexport type DialogMode = 'basic' | 'fullscreen';\n\nexport interface DialogStyleOptions {\n /**\n * Color for the OK/primary button (hex format, e.g., \"#FF5722\")\n */\n buttonColor?: string;\n /**\n * Color for the cancel button (hex format, e.g., \"#757575\")\n */\n cancelButtonColor?: string;\n /**\n * Color for the title text (hex format, e.g., \"#000000\")\n */\n titleColor?: string;\n /**\n * Color for the message text (hex format, e.g., \"#666666\")\n */\n messageColor?: string;\n /**\n * Background color of the dialog (hex format, e.g., \"#FFFFFF\")\n */\n backgroundColor?: string;\n /**\n * Font size for the title text in sp/pt (e.g., 20)\n */\n titleFontSize?: number;\n /**\n * Font size for the message text in sp/pt (e.g., 16)\n */\n messageFontSize?: number;\n /**\n * Font size for the button text in sp/pt (e.g., 14)\n */\n buttonFontSize?: number;\n /**\n * Vertical spacing between dialog content and the confirm/cancel buttons in dp/pt/px (e.g., 16).\n * Only applies to fullscreen and sheet modes.\n *\n * @example\n * // Fullscreen confirm with extra spacing between content and buttons\n * await ExtendedDialog.confirm({\n * title: 'Delete Account',\n * message: 'This action cannot be undone.',\n * mode: 'fullscreen',\n * contentButtonSpacing: 32,\n * });\n *\n * @example\n * // Sheet with tight spacing\n * await ExtendedDialog.sheet({\n * title: 'Order Summary',\n * rows: [{ title: 'Total', value: '$9.99' }],\n * contentButtonSpacing: 8,\n * });\n */\n contentButtonSpacing?: number;\n /**\n * Corner radius for the header logo in sheet dialogs (in dp/pt/px).\n * - Positive number: specific radius (e.g., 12)\n * - 0: square corners (no rounding)\n * - -1: full circle (radius = half of logo size)\n *\n * @default 8\n *\n * @example\n * // Custom rounded corners (12px)\n * await ExtendedDialog.sheet({\n * title: 'Settings',\n * headerLogo: 'https://example.com/logo.png',\n * headerLogoCornerRadius: 12,\n * rows: [],\n * });\n *\n * @example\n * // Full circle logo\n * await ExtendedDialog.sheet({\n * title: 'Profile',\n * headerLogo: 'https://example.com/avatar.png',\n * headerLogoCornerRadius: -1,\n * rows: [],\n * });\n *\n * @example\n * // Square logo (no rounding)\n * await ExtendedDialog.sheet({\n * title: 'Document',\n * headerLogo: 'https://example.com/icon.png',\n * headerLogoCornerRadius: 0,\n * rows: [],\n * });\n */\n headerLogoCornerRadius?: number;\n}\n\nexport interface BaseDialogOptions extends DialogStyleOptions {\n /**\n * Title displayed at the top of the dialog.\n */\n title?: string;\n /**\n * Message body displayed in the dialog.\n */\n message: string;\n /**\n * Presentation mode for the dialog.\n *\n * @default 'basic'\n */\n mode?: DialogMode;\n}\n\nexport interface AlertOptions extends BaseDialogOptions {\n /**\n * Title for the dismiss button.\n *\n * @default \"OK\"\n */\n buttonTitle?: string;\n}\n\nexport interface ConfirmOptions extends BaseDialogOptions {\n /**\n * Title for the confirmation button.\n *\n * @default \"OK\"\n */\n okButtonTitle?: string;\n /**\n * Title for the cancel button.\n *\n * @default \"Cancel\"\n */\n cancelButtonTitle?: string;\n}\n\nexport interface PromptOptions extends BaseDialogOptions {\n /**\n * Title for the confirmation button.\n *\n * @default \"OK\"\n */\n okButtonTitle?: string;\n /**\n * Title for the cancel button.\n *\n * @default \"Cancel\"\n */\n cancelButtonTitle?: string;\n /**\n * Placeholder text shown in the input field when empty.\n */\n inputPlaceholder?: string;\n /**\n * Pre-filled value for the input field.\n */\n inputText?: string;\n /**\n * Whether to automatically focus the input field and open the keyboard when the dialog appears.\n *\n * Note: On iOS, basic (non-fullscreen) dialogs always auto-focus the input field due to\n * UIAlertController's built-in behavior. This option only takes effect on iOS when using\n * fullscreen mode (`mode: 'fullscreen'`). On Android and web, this works for all modes.\n *\n * @default false\n */\n focusInput?: boolean;\n}\n\nexport interface SelectOption {\n /**\n * Display text for the option.\n */\n label: string;\n /**\n * Value returned when the option is selected.\n */\n value: string;\n}\n\nexport interface SingleSelectOptions extends BaseDialogOptions {\n /**\n * List of options to display for selection.\n */\n options: SelectOption[];\n /**\n * Value of the initially selected option.\n */\n selectedValue?: string;\n /**\n * Title for the confirmation button.\n *\n * @default \"OK\"\n */\n okButtonTitle?: string;\n /**\n * Title for the cancel button.\n *\n * @default \"Cancel\"\n */\n cancelButtonTitle?: string;\n}\n\nexport interface MultiSelectOptions extends BaseDialogOptions {\n /**\n * List of options to display for selection.\n */\n options: SelectOption[];\n /**\n * Values of the initially selected options.\n */\n selectedValues?: string[];\n /**\n * Title for the confirmation button.\n *\n * @default \"OK\"\n */\n okButtonTitle?: string;\n /**\n * Title for the cancel button.\n *\n * @default \"Cancel\"\n */\n cancelButtonTitle?: string;\n}\n\nexport interface ConfirmResult {\n /**\n * Whether the user confirmed (`true`) or cancelled (`false`).\n */\n value: boolean;\n}\n\nexport interface PromptResult {\n /**\n * The text entered by the user. Empty string if cancelled.\n */\n value: string;\n /**\n * Whether the user cancelled the dialog.\n */\n cancelled: boolean;\n}\n\nexport interface SingleSelectResult {\n /**\n * The value of the selected option, or `null` if cancelled.\n */\n value: string | null;\n /**\n * Whether the user cancelled the dialog.\n */\n cancelled: boolean;\n}\n\nexport interface MultiSelectResult {\n /**\n * The values of the selected options. Empty array if cancelled.\n */\n values: string[];\n /**\n * Whether the user cancelled the dialog.\n */\n cancelled: boolean;\n}\n\nexport interface SheetRow {\n /** Row title (required) */\n title: string;\n /** Optional logo/icon - supports base64 data URL or HTTP/HTTPS URL */\n logo?: string;\n /** Optional value displayed on the right */\n value?: string;\n}\n\nexport interface SheetOptions extends DialogStyleOptions {\n /** Header logo - supports base64 data URL or HTTP/HTTPS URL */\n headerLogo?: string;\n /** Sheet title */\n title: string;\n /** Optional subtitle displayed between title and rows */\n subtitle?: string;\n /** Description rows */\n rows: SheetRow[];\n /** Confirm button title */\n confirmButtonTitle?: string; // default: \"Confirm\"\n /** Cancel button title */\n cancelButtonTitle?: string; // default: \"Cancel\"\n /** Dialog mode */\n mode?: DialogMode;\n}\n\nexport interface MessageSheetOptions extends DialogStyleOptions {\n /** Header logo - supports base64 data URL or HTTP/HTTPS URL */\n headerLogo?: string;\n /** Sheet title */\n title: string;\n /** Optional subtitle displayed between title and message */\n subtitle?: string;\n /** Message body displayed in the sheet */\n message: string;\n /** Confirm button title */\n confirmButtonTitle?: string; // default: \"Confirm\"\n /** Cancel button title */\n cancelButtonTitle?: string; // default: \"Cancel\"\n /** Dialog mode */\n mode?: DialogMode;\n}\n\nexport interface SheetResult {\n /** True if confirmed, false if cancelled */\n confirmed: boolean;\n}\n\nexport interface MessageSheetResult {\n /** True if confirmed, false if cancelled */\n confirmed: boolean;\n}\n\nexport interface ExtendedDialogPlugin {\n /**\n * Show an alert dialog with a single dismiss button.\n *\n * @param options - Alert configuration options.\n */\n alert(options: AlertOptions): Promise<void>;\n /**\n * Show a confirmation dialog with OK and Cancel buttons.\n *\n * @param options - Confirm configuration options.\n * @returns The user's confirmation choice.\n */\n confirm(options: ConfirmOptions): Promise<ConfirmResult>;\n /**\n * Show a prompt dialog with a text input field.\n *\n * @param options - Prompt configuration options.\n * @returns The entered text and whether the dialog was cancelled.\n */\n prompt(options: PromptOptions): Promise<PromptResult>;\n /**\n * Show a single-select dialog where the user picks one option.\n *\n * @param options - Single select configuration options.\n * @returns The selected value and whether the dialog was cancelled.\n */\n singleSelect(options: SingleSelectOptions): Promise<SingleSelectResult>;\n /**\n * Show a multi-select dialog where the user picks one or more options.\n *\n * @param options - Multi select configuration options.\n * @returns The selected values and whether the dialog was cancelled.\n */\n multiSelect(options: MultiSelectOptions): Promise<MultiSelectResult>;\n /**\n * Show a sheet dialog with a header, title, and structured data rows.\n *\n * @param options - Sheet configuration options.\n * @returns Whether the user confirmed or cancelled the sheet.\n */\n sheet(options: SheetOptions): Promise<SheetResult>;\n\n /**\n * Show a message sheet dialog with an optional header logo, title, and message.\n *\n * @param options - Message sheet configuration options.\n * @returns Whether the user confirmed or cancelled the sheet.\n */\n messageSheet(options: MessageSheetOptions): Promise<MessageSheetResult>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Dialog presentation mode.\n *\n * - `'basic'` — Standard modal dialog (default)\n * - `'fullscreen'` — Full-screen dialog presentation\n */\nexport type DialogMode = 'basic' | 'fullscreen';\n\nexport interface DialogStyleOptions {\n /**\n * Color for the OK/primary button (hex format, e.g., \"#FF5722\")\n */\n buttonColor?: string;\n /**\n * Color for the cancel button (hex format, e.g., \"#757575\")\n */\n cancelButtonColor?: string;\n /**\n * Color for the title text (hex format, e.g., \"#000000\")\n */\n titleColor?: string;\n /**\n * Color for the message text (hex format, e.g., \"#666666\")\n */\n messageColor?: string;\n /**\n * Background color of the dialog (hex format, e.g., \"#FFFFFF\")\n */\n backgroundColor?: string;\n /**\n * Font size for the title text in sp/pt (e.g., 20)\n */\n titleFontSize?: number;\n /**\n * Font size for the message text in sp/pt (e.g., 16)\n */\n messageFontSize?: number;\n /**\n * Font size for the button text in sp/pt (e.g., 14)\n */\n buttonFontSize?: number;\n /**\n * Vertical spacing between dialog content and the confirm/cancel buttons in dp/pt/px (e.g., 16).\n * Only applies to fullscreen and sheet modes.\n *\n * @example\n * // Fullscreen confirm with extra spacing between content and buttons\n * await ExtendedDialog.confirm({\n * title: 'Delete Account',\n * message: 'This action cannot be undone.',\n * mode: 'fullscreen',\n * contentButtonSpacing: 32,\n * });\n *\n * @example\n * // Sheet with tight spacing\n * await ExtendedDialog.sheet({\n * title: 'Order Summary',\n * rows: [{ title: 'Total', value: '$9.99' }],\n * contentButtonSpacing: 8,\n * });\n */\n contentButtonSpacing?: number;\n /**\n * Corner radius for the header logo in sheet dialogs (in dp/pt/px).\n * - Positive number: specific radius (e.g., 12)\n * - 0: square corners (no rounding)\n * - -1: full circle (radius = half of logo size)\n *\n * @default 8\n *\n * @example\n * // Custom rounded corners (12px)\n * await ExtendedDialog.sheet({\n * title: 'Settings',\n * headerLogo: 'https://example.com/logo.png',\n * headerLogoCornerRadius: 12,\n * rows: [],\n * });\n *\n * @example\n * // Full circle logo\n * await ExtendedDialog.sheet({\n * title: 'Profile',\n * headerLogo: 'https://example.com/avatar.png',\n * headerLogoCornerRadius: -1,\n * rows: [],\n * });\n *\n * @example\n * // Square logo (no rounding)\n * await ExtendedDialog.sheet({\n * title: 'Document',\n * headerLogo: 'https://example.com/icon.png',\n * headerLogoCornerRadius: 0,\n * rows: [],\n * });\n */\n headerLogoCornerRadius?: number;\n}\n\nexport interface BaseDialogOptions extends DialogStyleOptions {\n /**\n * Title displayed at the top of the dialog.\n */\n title?: string;\n /**\n * Message body displayed in the dialog.\n */\n message: string;\n /**\n * Presentation mode for the dialog.\n *\n * @default 'basic'\n */\n mode?: DialogMode;\n}\n\nexport interface AlertOptions extends BaseDialogOptions {\n /**\n * Title for the dismiss button.\n *\n * @default \"OK\"\n */\n buttonTitle?: string;\n}\n\nexport interface ConfirmOptions extends BaseDialogOptions {\n /**\n * Title for the confirmation button.\n *\n * @default \"OK\"\n */\n okButtonTitle?: string;\n /**\n * Title for the cancel button.\n *\n * @default \"Cancel\"\n */\n cancelButtonTitle?: string;\n}\n\nexport interface PromptOptions extends BaseDialogOptions {\n /**\n * Title for the confirmation button.\n *\n * @default \"OK\"\n */\n okButtonTitle?: string;\n /**\n * Title for the cancel button.\n *\n * @default \"Cancel\"\n */\n cancelButtonTitle?: string;\n /**\n * Placeholder text shown in the input field when empty.\n */\n inputPlaceholder?: string;\n /**\n * Pre-filled value for the input field.\n */\n inputText?: string;\n /**\n * Whether to automatically focus the input field and open the keyboard when the dialog appears.\n *\n * Note: On iOS, basic (non-fullscreen) dialogs always auto-focus the input field due to\n * UIAlertController's built-in behavior. This option only takes effect on iOS when using\n * fullscreen mode (`mode: 'fullscreen'`). On Android and web, this works for all modes.\n *\n * @default false\n */\n focusInput?: boolean;\n}\n\nexport interface SelectOption {\n /**\n * Display text for the option.\n */\n label: string;\n /**\n * Value returned when the option is selected.\n */\n value: string;\n}\n\nexport interface SingleSelectOptions extends BaseDialogOptions {\n /**\n * List of options to display for selection.\n */\n options: SelectOption[];\n /**\n * Value of the initially selected option.\n */\n selectedValue?: string;\n /**\n * Title for the confirmation button.\n *\n * @default \"OK\"\n */\n okButtonTitle?: string;\n /**\n * Title for the cancel button.\n *\n * @default \"Cancel\"\n */\n cancelButtonTitle?: string;\n}\n\nexport interface MultiSelectOptions extends BaseDialogOptions {\n /**\n * List of options to display for selection.\n */\n options: SelectOption[];\n /**\n * Values of the initially selected options.\n */\n selectedValues?: string[];\n /**\n * Title for the confirmation button.\n *\n * @default \"OK\"\n */\n okButtonTitle?: string;\n /**\n * Title for the cancel button.\n *\n * @default \"Cancel\"\n */\n cancelButtonTitle?: string;\n}\n\nexport interface ConfirmResult {\n /**\n * Whether the user confirmed (`true`) or cancelled (`false`).\n */\n value: boolean;\n}\n\nexport interface PromptResult {\n /**\n * The text entered by the user. Empty string if cancelled.\n */\n value: string;\n /**\n * Whether the user cancelled the dialog.\n */\n cancelled: boolean;\n}\n\nexport interface SingleSelectResult {\n /**\n * The value of the selected option, or `null` if cancelled.\n */\n value: string | null;\n /**\n * Whether the user cancelled the dialog.\n */\n cancelled: boolean;\n}\n\nexport interface MultiSelectResult {\n /**\n * The values of the selected options. Empty array if cancelled.\n */\n values: string[];\n /**\n * Whether the user cancelled the dialog.\n */\n cancelled: boolean;\n}\n\nexport interface SheetRow {\n /** Row title (required) */\n title: string;\n /**\n * Optional logo/icon. Supports:\n * - PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL\n * - SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,<svg ...>...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg`\n */\n logo?: string;\n /** Optional value displayed on the right */\n value?: string;\n}\n\nexport interface SheetOptions extends DialogStyleOptions {\n /**\n * Header logo. Supports:\n * - PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL\n * - SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,<svg ...>...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg`\n */\n headerLogo?: string;\n /** Sheet title */\n title: string;\n /** Optional subtitle displayed between title and rows */\n subtitle?: string;\n /** Description rows */\n rows: SheetRow[];\n /** Confirm button title */\n confirmButtonTitle?: string; // default: \"Confirm\"\n /** Cancel button title */\n cancelButtonTitle?: string; // default: \"Cancel\"\n /** Dialog mode */\n mode?: DialogMode;\n}\n\nexport interface MessageSheetOptions extends DialogStyleOptions {\n /**\n * Header logo. Supports:\n * - PNG/JPEG/WebP/GIF as base64 data URL or HTTP/HTTPS URL\n * - SVG as `data:image/svg+xml;base64,...`, `data:image/svg+xml,<svg ...>...` (URL-encoded or plain), or any HTTP/HTTPS URL ending in `.svg`\n */\n headerLogo?: string;\n /** Sheet title */\n title: string;\n /** Optional subtitle displayed between title and message */\n subtitle?: string;\n /** Message body displayed in the sheet */\n message: string;\n /** Confirm button title */\n confirmButtonTitle?: string; // default: \"Confirm\"\n /** Cancel button title */\n cancelButtonTitle?: string; // default: \"Cancel\"\n /** Dialog mode */\n mode?: DialogMode;\n}\n\nexport interface SheetResult {\n /** True if confirmed, false if cancelled */\n confirmed: boolean;\n}\n\nexport interface MessageSheetResult {\n /** True if confirmed, false if cancelled */\n confirmed: boolean;\n}\n\nexport interface ExtendedDialogPlugin {\n /**\n * Show an alert dialog with a single dismiss button.\n *\n * @param options - Alert configuration options.\n */\n alert(options: AlertOptions): Promise<void>;\n /**\n * Show a confirmation dialog with OK and Cancel buttons.\n *\n * @param options - Confirm configuration options.\n * @returns The user's confirmation choice.\n */\n confirm(options: ConfirmOptions): Promise<ConfirmResult>;\n /**\n * Show a prompt dialog with a text input field.\n *\n * @param options - Prompt configuration options.\n * @returns The entered text and whether the dialog was cancelled.\n */\n prompt(options: PromptOptions): Promise<PromptResult>;\n /**\n * Show a single-select dialog where the user picks one option.\n *\n * @param options - Single select configuration options.\n * @returns The selected value and whether the dialog was cancelled.\n */\n singleSelect(options: SingleSelectOptions): Promise<SingleSelectResult>;\n /**\n * Show a multi-select dialog where the user picks one or more options.\n *\n * @param options - Multi select configuration options.\n * @returns The selected values and whether the dialog was cancelled.\n */\n multiSelect(options: MultiSelectOptions): Promise<MultiSelectResult>;\n /**\n * Show a sheet dialog with a header, title, and structured data rows.\n *\n * @param options - Sheet configuration options.\n * @returns Whether the user confirmed or cancelled the sheet.\n */\n sheet(options: SheetOptions): Promise<SheetResult>;\n\n /**\n * Show a message sheet dialog with an optional header logo, title, and message.\n *\n * @param options - Message sheet configuration options.\n * @returns Whether the user confirmed or cancelled the sheet.\n */\n messageSheet(options: MessageSheetOptions): Promise<MessageSheetResult>;\n}\n"]}
@@ -946,25 +946,30 @@ public class FullScreenDialogViewController: UIViewController {
946
946
  }
947
947
 
948
948
  private func loadImage(from urlString: String, into imageView: UIImageView) {
949
- // Check if it's a base64 data URL
950
- if urlString.hasPrefix("data:") {
951
- if let commaIndex = urlString.firstIndex(of: ",") {
952
- let base64String = String(urlString[urlString.index(after: commaIndex)...])
953
- if let imageData = Data(base64Encoded: base64String),
954
- let image = UIImage(data: imageData) {
955
- imageView.image = image
956
- }
949
+ if SvgImageLoader.isSvgSource(urlString) {
950
+ let targetSize = imageView.bounds.size == .zero
951
+ ? CGSize(width: 64, height: 64)
952
+ : imageView.bounds.size
953
+ SvgImageLoader.render(source: urlString, targetSize: targetSize) { [weak imageView] image in
954
+ imageView?.image = image
957
955
  }
958
- } else if let url = URL(string: urlString) {
959
- // Load from HTTP/HTTPS URL asynchronously
960
- URLSession.shared.dataTask(with: url) { data, _, _ in
961
- if let data = data, let image = UIImage(data: data) {
962
- DispatchQueue.main.async {
963
- imageView.image = image
964
- }
965
- }
966
- }.resume()
956
+ return
957
+ }
958
+ if SvgImageLoader.isDataUrl(urlString) {
959
+ if let imageData = SvgImageLoader.decodeDataUrl(urlString),
960
+ let image = UIImage(data: imageData) {
961
+ imageView.image = image
962
+ }
963
+ return
967
964
  }
965
+ guard let url = URL(string: urlString) else { return }
966
+ URLSession.shared.dataTask(with: url) { [weak imageView] data, _, _ in
967
+ if let data = data, let image = UIImage(data: data) {
968
+ DispatchQueue.main.async {
969
+ imageView?.image = image
970
+ }
971
+ }
972
+ }.resume()
968
973
  }
969
974
 
970
975
  private func applyLiquidGlassStyle() {
@@ -0,0 +1,70 @@
1
+ import UIKit
2
+ import SVGKit
3
+
4
+ enum SvgImageLoader {
5
+
6
+ private static let svgDataUrlPrefix = "data:image/svg+xml"
7
+ private static let dataUrlPrefix = "data:"
8
+
9
+ static func isDataUrl(_ source: String) -> Bool {
10
+ source.range(of: dataUrlPrefix, options: [.anchored, .caseInsensitive]) != nil
11
+ }
12
+
13
+ static func isSvgSource(_ source: String) -> Bool {
14
+ if source.range(of: svgDataUrlPrefix, options: [.anchored, .caseInsensitive]) != nil {
15
+ return true
16
+ }
17
+ if let url = URL(string: source),
18
+ let scheme = url.scheme?.lowercased(),
19
+ scheme == "http" || scheme == "https" {
20
+ return url.pathExtension.caseInsensitiveCompare("svg") == .orderedSame
21
+ }
22
+ return false
23
+ }
24
+
25
+ /// Supports `;base64` and percent-encoded payloads.
26
+ static func decodeDataUrl(_ source: String) -> Data? {
27
+ guard let commaIndex = source.firstIndex(of: ",") else { return nil }
28
+ let header = source[..<commaIndex]
29
+ let payload = String(source[source.index(after: commaIndex)...])
30
+ if header.range(of: ";base64", options: .caseInsensitive) != nil {
31
+ return Data(base64Encoded: payload)
32
+ }
33
+ return (payload.removingPercentEncoding ?? payload).data(using: .utf8)
34
+ }
35
+
36
+ /// Uses image's intrinsic size when `targetSize` is `.zero`.
37
+ static func render(source: String, targetSize: CGSize, completion: @escaping (UIImage?) -> Void) {
38
+ if isDataUrl(source) {
39
+ guard let data = decodeDataUrl(source) else {
40
+ completion(nil); return
41
+ }
42
+ DispatchQueue.global(qos: .userInitiated).async {
43
+ let image = render(data: data, targetSize: targetSize)
44
+ DispatchQueue.main.async { completion(image) }
45
+ }
46
+ return
47
+ }
48
+ guard let url = URL(string: source) else {
49
+ completion(nil); return
50
+ }
51
+ URLSession.shared.dataTask(with: url) { data, _, _ in
52
+ guard let data = data else {
53
+ DispatchQueue.main.async { completion(nil) }
54
+ return
55
+ }
56
+ DispatchQueue.global(qos: .userInitiated).async {
57
+ let image = render(data: data, targetSize: targetSize)
58
+ DispatchQueue.main.async { completion(image) }
59
+ }
60
+ }.resume()
61
+ }
62
+
63
+ private static func render(data: Data, targetSize: CGSize) -> UIImage? {
64
+ guard let svgImage = SVGKImage(data: data) else { return nil }
65
+ if targetSize.width > 0 && targetSize.height > 0 && svgImage.size != targetSize {
66
+ svgImage.size = targetSize
67
+ }
68
+ return svgImage.uiImage
69
+ }
70
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-extended-dialog",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "Show native dialogs and sheets in Capacitor with Material 3 and Liquid Glass UI",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",