@memberjunction/ng-shared-generic 5.37.0 → 5.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -162,5 +162,5 @@ For the full token catalog, architecture details, custom theme creation guide, a
162
162
 
163
163
  ## Dependencies
164
164
 
165
- - [@memberjunction/core](../../MJCore/README.md) -- Core MJ framework
166
- - [@memberjunction/core-entities](../../MJCoreEntities/README.md) -- Entity types
165
+ - [@memberjunction/core](../../../MJCore/readme.md) -- Core MJ framework
166
+ - [@memberjunction/core-entities](../../../MJCoreEntities/readme.md) -- Entity types
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rich-text-sanitize.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-text-sanitize.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/rich-text-sanitize.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect } from 'vitest';
3
+ import { PurifyRichTextHtml } from '../lib/rich-text/rich-text-sanitize';
4
+ describe('PurifyRichTextHtml', () => {
5
+ describe('strips dangerous vectors', () => {
6
+ it('removes <script> tags', () => {
7
+ const out = PurifyRichTextHtml('<p>ok</p><script>alert(1)</script>');
8
+ expect(out).toContain('<p>ok</p>');
9
+ expect(out.toLowerCase()).not.toContain('<script');
10
+ expect(out).not.toContain('alert(1)');
11
+ });
12
+ it('strips on* event handler attributes', () => {
13
+ const out = PurifyRichTextHtml('<img src="x" onerror="alert(1)">');
14
+ expect(out.toLowerCase()).not.toContain('onerror');
15
+ });
16
+ it('strips javascript: URLs', () => {
17
+ const out = PurifyRichTextHtml('<a href="javascript:alert(1)">x</a>');
18
+ expect(out.toLowerCase()).not.toContain('javascript:');
19
+ });
20
+ it('removes <script> embedded inside SVG', () => {
21
+ const out = PurifyRichTextHtml('<svg><script>alert(1)</script><rect/></svg>');
22
+ expect(out.toLowerCase()).not.toContain('<script');
23
+ expect(out).not.toContain('alert(1)');
24
+ });
25
+ });
26
+ describe('keeps safe rich content', () => {
27
+ it('preserves structural HTML', () => {
28
+ const out = PurifyRichTextHtml('<h2>Title</h2><ul><li>a</li></ul><table><tr><td>c</td></tr></table>');
29
+ expect(out).toContain('<h2>Title</h2>');
30
+ expect(out).toContain('<li>a</li>');
31
+ expect(out).toContain('<td>c</td>');
32
+ });
33
+ it('preserves inline SVG shapes (the whole point of swapping the sanitizer)', () => {
34
+ const svg = '<svg viewBox="0 0 10 10"><defs><linearGradient id="g"><stop offset="0%"/></linearGradient></defs><rect fill="url(#g)"/><path d="M0 0 L10 10"/><circle cx="5" cy="5" r="3"/></svg>';
35
+ const out = PurifyRichTextHtml(svg);
36
+ expect(out.toLowerCase()).toContain('<svg');
37
+ expect(out.toLowerCase()).toContain('<rect');
38
+ expect(out.toLowerCase()).toContain('<path');
39
+ expect(out.toLowerCase()).toContain('<circle');
40
+ expect(out.toLowerCase()).toContain('lineargradient');
41
+ });
42
+ });
43
+ describe('edge cases', () => {
44
+ it('returns empty string for null/undefined/empty', () => {
45
+ expect(PurifyRichTextHtml(null)).toBe('');
46
+ expect(PurifyRichTextHtml(undefined)).toBe('');
47
+ expect(PurifyRichTextHtml('')).toBe('');
48
+ });
49
+ it('passes plain text through unchanged', () => {
50
+ expect(PurifyRichTextHtml('just text')).toBe('just text');
51
+ });
52
+ });
53
+ });
54
+ //# sourceMappingURL=rich-text-sanitize.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-text-sanitize.test.js","sourceRoot":"","sources":["../../src/__tests__/rich-text-sanitize.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment jsdom\nimport { describe, it, expect } from 'vitest';\nimport { PurifyRichTextHtml } from '../lib/rich-text/rich-text-sanitize';\n\ndescribe('PurifyRichTextHtml', () => {\n describe('strips dangerous vectors', () => {\n it('removes <script> tags', () => {\n const out = PurifyRichTextHtml('<p>ok</p><script>alert(1)</script>');\n expect(out).toContain('<p>ok</p>');\n expect(out.toLowerCase()).not.toContain('<script');\n expect(out).not.toContain('alert(1)');\n });\n\n it('strips on* event handler attributes', () => {\n const out = PurifyRichTextHtml('<img src=\"x\" onerror=\"alert(1)\">');\n expect(out.toLowerCase()).not.toContain('onerror');\n });\n\n it('strips javascript: URLs', () => {\n const out = PurifyRichTextHtml('<a href=\"javascript:alert(1)\">x</a>');\n expect(out.toLowerCase()).not.toContain('javascript:');\n });\n\n it('removes <script> embedded inside SVG', () => {\n const out = PurifyRichTextHtml('<svg><script>alert(1)</script><rect/></svg>');\n expect(out.toLowerCase()).not.toContain('<script');\n expect(out).not.toContain('alert(1)');\n });\n });\n\n describe('keeps safe rich content', () => {\n it('preserves structural HTML', () => {\n const out = PurifyRichTextHtml('<h2>Title</h2><ul><li>a</li></ul><table><tr><td>c</td></tr></table>');\n expect(out).toContain('<h2>Title</h2>');\n expect(out).toContain('<li>a</li>');\n expect(out).toContain('<td>c</td>');\n });\n\n it('preserves inline SVG shapes (the whole point of swapping the sanitizer)', () => {\n const svg = '<svg viewBox=\"0 0 10 10\"><defs><linearGradient id=\"g\"><stop offset=\"0%\"/></linearGradient></defs><rect fill=\"url(#g)\"/><path d=\"M0 0 L10 10\"/><circle cx=\"5\" cy=\"5\" r=\"3\"/></svg>';\n const out = PurifyRichTextHtml(svg);\n expect(out.toLowerCase()).toContain('<svg');\n expect(out.toLowerCase()).toContain('<rect');\n expect(out.toLowerCase()).toContain('<path');\n expect(out.toLowerCase()).toContain('<circle');\n expect(out.toLowerCase()).toContain('lineargradient');\n });\n });\n\n describe('edge cases', () => {\n it('returns empty string for null/undefined/empty', () => {\n expect(PurifyRichTextHtml(null)).toBe('');\n expect(PurifyRichTextHtml(undefined)).toBe('');\n expect(PurifyRichTextHtml('')).toBe('');\n });\n\n it('passes plain text through unchanged', () => {\n expect(PurifyRichTextHtml('just text')).toBe('just text');\n });\n });\n});\n"]}
@@ -211,6 +211,11 @@
211
211
  --mj-highlight-subtle: var(--mj-brand-accent-subtle);
212
212
  --mj-on-highlight: var(--mj-brand-on-accent);
213
213
 
214
+ /* Search-match highlight — the <mark> wrapping typed-query matches in lists,
215
+ field labels, FK dropdowns, etc. Muted amber (not the garish browser default). */
216
+ --mj-search-highlight-bg: color-mix(in srgb, var(--mj-status-warning) 24%, transparent);
217
+ --mj-search-highlight-text: var(--mj-text-primary);
218
+
214
219
  /* Login Banner Gradient & Waves */
215
220
  --mj-login-grad-start: var(--mj-color-brand-900);
216
221
  --mj-login-grad-mid: var(--mj-color-brand-700);
@@ -413,6 +418,10 @@
413
418
  --mj-status-warning-text: var(--mj-color-warning-100);
414
419
  --mj-status-warning-border: var(--mj-color-warning-600);
415
420
 
421
+ /* Slightly stronger search highlight on dark surfaces so it stays visible. */
422
+ --mj-search-highlight-bg: color-mix(in srgb, var(--mj-status-warning) 30%, transparent);
423
+ --mj-search-highlight-text: var(--mj-text-primary);
424
+
416
425
  --mj-status-error-bg: rgba(239, 68, 68, 0.15);
417
426
  --mj-status-error-text: var(--mj-color-error-100);
418
427
  --mj-status-error-border: var(--mj-color-error-600);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Sanitize untrusted HTML for safe rich-text rendering.
3
+ *
4
+ * Use this instead of relying on Angular's built-in `[innerHTML]` sanitizer
5
+ * (which strips ALL SVG and inline styles). Configured for the HTML + SVG
6
+ * profiles, DOMPurify keeps safe structural HTML **and** inline vector graphics
7
+ * (`<svg>`, `<path>`, gradients, `<text>`, …) and inline `style`, while removing
8
+ * the dangerous vectors: `<script>`, `on*` event handlers, `javascript:`/`data:`
9
+ * script URLs, `<foreignObject>`, and friends.
10
+ *
11
+ * Because DOMPurify has already neutralized the markup, a caller may then bind
12
+ * the result via `DomSanitizer.bypassSecurityTrustHtml(...)` — the documented
13
+ * safe pattern (sanitize first, then bypass Angular's redundant re-sanitize).
14
+ * The {@link MJSafeRichHtmlPipe} pipe does exactly that; prefer it in templates.
15
+ *
16
+ * Safe for untrusted input by design, so it suits both explicitly-typed HTML
17
+ * fields and content that was only heuristically detected as HTML.
18
+ *
19
+ * @param raw the raw HTML string (null/undefined → '')
20
+ * @returns a sanitized HTML string safe to render
21
+ */
22
+ export declare function PurifyRichTextHtml(raw: string | null | undefined): string;
23
+ //# sourceMappingURL=rich-text-sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-text-sanitize.d.ts","sourceRoot":"","sources":["../../../src/lib/rich-text/rich-text-sanitize.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAIzE"}
@@ -0,0 +1,28 @@
1
+ import DOMPurify from 'dompurify';
2
+ /**
3
+ * Sanitize untrusted HTML for safe rich-text rendering.
4
+ *
5
+ * Use this instead of relying on Angular's built-in `[innerHTML]` sanitizer
6
+ * (which strips ALL SVG and inline styles). Configured for the HTML + SVG
7
+ * profiles, DOMPurify keeps safe structural HTML **and** inline vector graphics
8
+ * (`<svg>`, `<path>`, gradients, `<text>`, …) and inline `style`, while removing
9
+ * the dangerous vectors: `<script>`, `on*` event handlers, `javascript:`/`data:`
10
+ * script URLs, `<foreignObject>`, and friends.
11
+ *
12
+ * Because DOMPurify has already neutralized the markup, a caller may then bind
13
+ * the result via `DomSanitizer.bypassSecurityTrustHtml(...)` — the documented
14
+ * safe pattern (sanitize first, then bypass Angular's redundant re-sanitize).
15
+ * The {@link MJSafeRichHtmlPipe} pipe does exactly that; prefer it in templates.
16
+ *
17
+ * Safe for untrusted input by design, so it suits both explicitly-typed HTML
18
+ * fields and content that was only heuristically detected as HTML.
19
+ *
20
+ * @param raw the raw HTML string (null/undefined → '')
21
+ * @returns a sanitized HTML string safe to render
22
+ */
23
+ export function PurifyRichTextHtml(raw) {
24
+ return DOMPurify.sanitize(raw ?? '', {
25
+ USE_PROFILES: { html: true, svg: true, svgFilters: true },
26
+ });
27
+ }
28
+ //# sourceMappingURL=rich-text-sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-text-sanitize.js","sourceRoot":"","sources":["../../../src/lib/rich-text/rich-text-sanitize.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAA8B;IAC7D,OAAO,SAAS,CAAC,QAAQ,CAAC,GAAG,IAAI,EAAE,EAAE;QACjC,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;KAC5D,CAAC,CAAC;AACP,CAAC","sourcesContent":["import DOMPurify from 'dompurify';\n\n/**\n * Sanitize untrusted HTML for safe rich-text rendering.\n *\n * Use this instead of relying on Angular's built-in `[innerHTML]` sanitizer\n * (which strips ALL SVG and inline styles). Configured for the HTML + SVG\n * profiles, DOMPurify keeps safe structural HTML **and** inline vector graphics\n * (`<svg>`, `<path>`, gradients, `<text>`, …) and inline `style`, while removing\n * the dangerous vectors: `<script>`, `on*` event handlers, `javascript:`/`data:`\n * script URLs, `<foreignObject>`, and friends.\n *\n * Because DOMPurify has already neutralized the markup, a caller may then bind\n * the result via `DomSanitizer.bypassSecurityTrustHtml(...)` — the documented\n * safe pattern (sanitize first, then bypass Angular's redundant re-sanitize).\n * The {@link MJSafeRichHtmlPipe} pipe does exactly that; prefer it in templates.\n *\n * Safe for untrusted input by design, so it suits both explicitly-typed HTML\n * fields and content that was only heuristically detected as HTML.\n *\n * @param raw the raw HTML string (null/undefined → '')\n * @returns a sanitized HTML string safe to render\n */\nexport function PurifyRichTextHtml(raw: string | null | undefined): string {\n return DOMPurify.sanitize(raw ?? '', {\n USE_PROFILES: { html: true, svg: true, svgFilters: true },\n });\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import { PipeTransform } from '@angular/core';
2
+ import { SafeHtml } from '@angular/platform-browser';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Renders untrusted HTML safely — keeping SVG and richer markup — for use with
6
+ * `[innerHTML]`. Sanitizes with DOMPurify (HTML + SVG profiles) and then wraps
7
+ * the cleaned string with `bypassSecurityTrustHtml`, so Angular doesn't strip
8
+ * the now-safe SVG/styles a second time.
9
+ *
10
+ * Pure pipe → Angular memoizes by input value, so it only re-sanitizes when the
11
+ * string actually changes (cheap under OnPush).
12
+ *
13
+ * @example
14
+ * ```html
15
+ * <div [innerHTML]="someHtmlString | mjSafeRichHtml"></div>
16
+ * ```
17
+ */
18
+ export declare class MJSafeRichHtmlPipe implements PipeTransform {
19
+ private sanitizer;
20
+ transform(value: string | null | undefined): SafeHtml;
21
+ static ɵfac: i0.ɵɵFactoryDeclaration<MJSafeRichHtmlPipe, never>;
22
+ static ɵpipe: i0.ɵɵPipeDeclaration<MJSafeRichHtmlPipe, "mjSafeRichHtml", true>;
23
+ }
24
+ //# sourceMappingURL=safe-rich-html.pipe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-rich-html.pipe.d.ts","sourceRoot":"","sources":["../../../src/lib/rich-text/safe-rich-html.pipe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,aAAa,EAAU,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAgB,QAAQ,EAAE,MAAM,2BAA2B,CAAC;;AAGnE;;;;;;;;;;;;;GAaG;AACH,qBAKa,kBAAmB,YAAW,aAAa;IACpD,OAAO,CAAC,SAAS,CAAwB;IAEzC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ;yCAH5C,kBAAkB;uCAAlB,kBAAkB;CAM9B"}
@@ -0,0 +1,35 @@
1
+ import { Pipe, inject } from '@angular/core';
2
+ import { DomSanitizer } from '@angular/platform-browser';
3
+ import { PurifyRichTextHtml } from './rich-text-sanitize';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Renders untrusted HTML safely — keeping SVG and richer markup — for use with
7
+ * `[innerHTML]`. Sanitizes with DOMPurify (HTML + SVG profiles) and then wraps
8
+ * the cleaned string with `bypassSecurityTrustHtml`, so Angular doesn't strip
9
+ * the now-safe SVG/styles a second time.
10
+ *
11
+ * Pure pipe → Angular memoizes by input value, so it only re-sanitizes when the
12
+ * string actually changes (cheap under OnPush).
13
+ *
14
+ * @example
15
+ * ```html
16
+ * <div [innerHTML]="someHtmlString | mjSafeRichHtml"></div>
17
+ * ```
18
+ */
19
+ export class MJSafeRichHtmlPipe {
20
+ sanitizer = inject(DomSanitizer);
21
+ transform(value) {
22
+ return this.sanitizer.bypassSecurityTrustHtml(PurifyRichTextHtml(value));
23
+ }
24
+ static ɵfac = function MJSafeRichHtmlPipe_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJSafeRichHtmlPipe)(); };
25
+ static ɵpipe = /*@__PURE__*/ i0.ɵɵdefinePipe({ name: "mjSafeRichHtml", type: MJSafeRichHtmlPipe, pure: true });
26
+ }
27
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJSafeRichHtmlPipe, [{
28
+ type: Pipe,
29
+ args: [{
30
+ name: 'mjSafeRichHtml',
31
+ standalone: true,
32
+ pure: true,
33
+ }]
34
+ }], null, null); })();
35
+ //# sourceMappingURL=safe-rich-html.pipe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-rich-html.pipe.js","sourceRoot":"","sources":["../../../src/lib/rich-text/safe-rich-html.pipe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAiB,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAY,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;;AAE1D;;;;;;;;;;;;;GAaG;AAMH,MAAM,OAAO,kBAAkB;IACnB,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IAEzC,SAAS,CAAC,KAAgC;QACtC,OAAO,IAAI,CAAC,SAAS,CAAC,uBAAuB,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7E,CAAC;4GALQ,kBAAkB;iFAAlB,kBAAkB;;iFAAlB,kBAAkB;cAL9B,IAAI;eAAC;gBACF,IAAI,EAAE,gBAAgB;gBACtB,UAAU,EAAE,IAAI;gBAChB,IAAI,EAAE,IAAI;aACb","sourcesContent":["import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { PurifyRichTextHtml } from './rich-text-sanitize';\n\n/**\n * Renders untrusted HTML safely — keeping SVG and richer markup — for use with\n * `[innerHTML]`. Sanitizes with DOMPurify (HTML + SVG profiles) and then wraps\n * the cleaned string with `bypassSecurityTrustHtml`, so Angular doesn't strip\n * the now-safe SVG/styles a second time.\n *\n * Pure pipe → Angular memoizes by input value, so it only re-sanitizes when the\n * string actually changes (cheap under OnPush).\n *\n * @example\n * ```html\n * <div [innerHTML]=\"someHtmlString | mjSafeRichHtml\"></div>\n * ```\n */\n@Pipe({\n name: 'mjSafeRichHtml',\n standalone: true,\n pure: true,\n})\nexport class MJSafeRichHtmlPipe implements PipeTransform {\n private sanitizer = inject(DomSanitizer);\n\n transform(value: string | null | undefined): SafeHtml {\n return this.sanitizer.bypassSecurityTrustHtml(PurifyRichTextHtml(value));\n }\n}\n"]}
@@ -2,4 +2,6 @@ export * from './lib/module';
2
2
  export * from './lib/recent-access.service';
3
3
  export * from './lib/theme.service';
4
4
  export * from './lib/loading/loading.component';
5
+ export * from './lib/rich-text/rich-text-sanitize';
6
+ export * from './lib/rich-text/safe-rich-html.pipe';
5
7
  //# sourceMappingURL=public-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAKA,cAAc,cAAc,CAAC;AAG7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AAGpC,cAAc,iCAAiC,CAAC"}
1
+ {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAKA,cAAc,cAAc,CAAC;AAG7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AAGpC,cAAc,iCAAiC,CAAC;AAGhD,cAAc,oCAAoC,CAAC;AACnD,cAAc,qCAAqC,CAAC"}
@@ -8,4 +8,7 @@ export * from './lib/recent-access.service';
8
8
  export * from './lib/theme.service';
9
9
  // Components
10
10
  export * from './lib/loading/loading.component';
11
+ // Rich-text rendering (safe HTML/SVG sanitization)
12
+ export * from './lib/rich-text/rich-text-sanitize';
13
+ export * from './lib/rich-text/safe-rich-html.pipe';
11
14
  //# sourceMappingURL=public-api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,SAAS;AACT,cAAc,cAAc,CAAC;AAE7B,WAAW;AACX,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AAEpC,aAAa;AACb,cAAc,iCAAiC,CAAC","sourcesContent":["/*\n * Public API Surface for @memberjunction/ng-shared-generic\n */\n\n// Module\nexport * from './lib/module';\n\n// Services\nexport * from './lib/recent-access.service';\nexport * from './lib/theme.service';\n\n// Components\nexport * from './lib/loading/loading.component';\n"]}
1
+ {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,SAAS;AACT,cAAc,cAAc,CAAC;AAE7B,WAAW;AACX,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AAEpC,aAAa;AACb,cAAc,iCAAiC,CAAC;AAEhD,mDAAmD;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,qCAAqC,CAAC","sourcesContent":["/*\n * Public API Surface for @memberjunction/ng-shared-generic\n */\n\n// Module\nexport * from './lib/module';\n\n// Services\nexport * from './lib/recent-access.service';\nexport * from './lib/theme.service';\n\n// Components\nexport * from './lib/loading/loading.component';\n\n// Rich-text rendering (safe HTML/SVG sanitization)\nexport * from './lib/rich-text/rich-text-sanitize';\nexport * from './lib/rich-text/safe-rich-html.pipe';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-shared-generic",
3
- "version": "5.37.0",
3
+ "version": "5.39.0",
4
4
  "description": "MemberJunction: Generic Angular Shared Package - utility services and reusable elements used in any Angular application.",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -8,7 +8,7 @@
8
8
  "/dist"
9
9
  ],
10
10
  "scripts": {
11
- "test": "echo \"No tests configured yet\"",
11
+ "test": "vitest run",
12
12
  "build": "npm run clean && ngc && npm run copy-assets",
13
13
  "copy-assets": "cpy 'src/lib/_tokens.scss' dist/lib --flat",
14
14
  "clean": "rimraf dist",
@@ -28,13 +28,15 @@
28
28
  "peerDependencies": {
29
29
  "@angular/common": "21.1.3",
30
30
  "@angular/core": "21.1.3",
31
+ "@angular/platform-browser": "21.1.3",
31
32
  "rxjs": "^7.8.2"
32
33
  },
33
34
  "dependencies": {
34
- "@memberjunction/core": "5.37.0",
35
- "@memberjunction/ng-base-types": "5.37.0",
36
- "@memberjunction/core-entities": "5.37.0",
37
- "@memberjunction/global": "5.37.0",
35
+ "@memberjunction/core": "5.39.0",
36
+ "@memberjunction/ng-base-types": "5.39.0",
37
+ "@memberjunction/core-entities": "5.39.0",
38
+ "@memberjunction/global": "5.39.0",
39
+ "dompurify": "^3.3.0",
38
40
  "tslib": "^2.8.1"
39
41
  },
40
42
  "sideEffects": false,