@claudebernard/therapeutic-alternative-element 2.3.0 → 2.5.0-beta.1

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.
@@ -12,11 +12,18 @@ import { TemplateResult } from 'lit-html';
12
12
  * @element bcb-therapeutic-alternative
13
13
  */
14
14
  export declare class TherapeuticAlternative extends LitElement {
15
- hasTitle: boolean;
16
15
  medication: Bundle | null;
17
16
  patientProfile: Bundle | null;
18
17
  apiUrl: string;
19
18
  apiKey: string | undefined;
19
+ /**
20
+ * Contexte d'intégration (application consommatrice + chaîne de web components BCB parents)
21
+ * transmis par l'intégrateur, propagé sur chaque appel API sortant via des en-têtes dédiés -
22
+ * voir `@shared/utils/tracing`. N'inclut pas ce composant lui-même dans `ancestry`.
23
+ */
24
+ context: WebComponentContext | undefined;
25
+ /* Excluded from this release type: showBetaBadge */
26
+ private isBeta;
20
27
  private sourceProduct;
21
28
  private results;
22
29
  private totalElements;
@@ -46,12 +53,13 @@ export declare class TherapeuticAlternative extends LitElement {
46
53
  private apiReplaceError;
47
54
  private apiReplaceLoading;
48
55
  private missingPrescriptionError;
49
- private hasAppliedInitialScope;
50
56
  private refetchTimer;
51
57
  private requestSeq;
52
58
  private get currentMedication();
53
59
  private get currentMedicationRequest();
54
60
  private get sourceProductId();
61
+ private get sourceProductLabel();
62
+ private get isParaSource();
55
63
  updated(changedProperties: Map<string | number | symbol, unknown>): void;
56
64
  private resetAllCriteria;
57
65
  disconnectedCallback(): void;
@@ -64,7 +72,7 @@ export declare class TherapeuticAlternative extends LitElement {
64
72
  private static buildIncludeFacets;
65
73
  private static buildExcludeFacets;
66
74
  private static codesOf;
67
- private static deriveInitialScope;
75
+ private scopePossessedIds;
68
76
  private includePossessedIds;
69
77
  private excludePossessedIds;
70
78
  private toggleScope;
@@ -93,11 +101,32 @@ export declare class TherapeuticAlternative extends LitElement {
93
101
  private renderSectionHeader;
94
102
  private matchReasonLabels;
95
103
  private renderResults;
96
- private renderTitle;
104
+ private renderBetaBadge;
97
105
  render(): TemplateResult<1>;
98
106
  static styles: CSSResult;
99
107
  }
100
108
 
109
+ /**
110
+ * Contexte d'intégration porté par une instance du composant : quelle application consommatrice
111
+ * (Equasens, Maiia officine, ...) et quelle chaîne de web components BCB imbriqués ont mené jusqu'à
112
+ * cet appel. Les deux champs sont optionnels et indépendants - un intégrateur peut renseigner l'un
113
+ * sans l'autre.
114
+ */
115
+ declare interface WebComponentContext {
116
+ /** Application consommatrice, ex: "equasens" ou "equasens@1.3.1". Envoyé tel quel. */
117
+ consumer?: string;
118
+ /**
119
+ * Chaîne des web components BCB parents, du plus extérieur au plus proche - N'INCLUT PAS le
120
+ * composant courant. Ex: pour Equasens -> shared-medication-review -> medication-request-editor
121
+ * -> ce composant, `ancestry` vaut [{name: "shared-medication-review", ...}, {name:
122
+ * "medication-request-editor", ...}].
123
+ */
124
+ ancestry?: Array<{
125
+ name: string;
126
+ version: string;
127
+ }>;
128
+ }
129
+
101
130
 
102
131
  export * from "@claudebernard/web-component-fhir-utils";
103
132
 
@@ -775,6 +775,67 @@ const getFhirPosologies = (options) => (options?.client ?? client$1).post({
775
775
  ...options?.headers
776
776
  }
777
777
  });
778
+ function random$1(len) {
779
+ return crypto.getRandomValues(new Uint8Array(len));
780
+ }
781
+ const W3C_TRACEPARENT_VERSION = "00";
782
+ const FLAG_SAMPLE = 1;
783
+ const FLAG_RANDOM = 2;
784
+ function traceparent(version, trace_id, parent_id, flags) {
785
+ return {
786
+ version,
787
+ trace_id,
788
+ parent_id,
789
+ flags,
790
+ child() {
791
+ return traceparent(this.version, this.trace_id, random(8), this.flags);
792
+ },
793
+ toString() {
794
+ let flags2 = this.flags.toString(16).padStart(2, "0");
795
+ return `${this.version}-${this.trace_id}-${this.parent_id}-${flags2}`;
796
+ }
797
+ };
798
+ }
799
+ function make() {
800
+ let id = random(24);
801
+ return traceparent(W3C_TRACEPARENT_VERSION, id.slice(0, 32), id.slice(32), FLAG_SAMPLE | FLAG_RANDOM);
802
+ }
803
+ let IDX = 256, HEX = [];
804
+ while (IDX--) HEX[IDX] = (IDX + 256).toString(16).substring(1);
805
+ function random(size) {
806
+ let a2 = random$1(size), i3 = 0, o2 = "";
807
+ for (; i3 < a2.length; i3++) o2 += HEX[a2[i3]];
808
+ return o2;
809
+ }
810
+ const COMPONENT_HEADERS = {
811
+ "Bcb-Web-Component-Package": "@claudebernard/therapeutic-alternative-element",
812
+ "Bcb-Web-Component-Version": "2.5"
813
+ };
814
+ let activeContext;
815
+ function setActiveContext(context) {
816
+ activeContext = context;
817
+ }
818
+ function makeTraceparent() {
819
+ return make().child().toString();
820
+ }
821
+ function buildTracingHeaders() {
822
+ return {
823
+ traceparent: makeTraceparent(),
824
+ ...COMPONENT_HEADERS,
825
+ ...activeContext?.consumer ? { "Bcb-Consumer-Product": activeContext.consumer } : {},
826
+ ...activeContext?.ancestry?.length ? { "Bcb-Web-Component-Chain": activeContext.ancestry.map(({ name, version }) => `${name}@${version}`).join(",") } : {}
827
+ };
828
+ }
829
+ function attachTracingHeaders(request) {
830
+ const defaults = buildTracingHeaders();
831
+ for (const [key, value] of Object.entries(defaults)) {
832
+ if (!request.headers.has(key)) {
833
+ request.headers.set(key, value);
834
+ }
835
+ }
836
+ return request;
837
+ }
838
+ client$1.interceptors.request.use(attachTracingHeaders);
778
839
  const prescriptionApi = {
779
840
  getFhirPosologies
780
841
  };
@@ -1559,6 +1620,7 @@ const searchProductsById7 = (options) => (options.client ?? client).post({
1559
1620
  ...options.headers
1560
1621
  }
1561
1622
  });
1623
+ client.interceptors.request.use(attachTracingHeaders);
1562
1624
  const searchApi = {
1563
1625
  searchProductsByIndication: searchProductsById7,
1564
1626
  searchAlternative
@@ -1642,7 +1704,7 @@ function findMostAdequateMedicationRequest(entries, threshold = DEFAULT_ADEQUATI
1642
1704
  const medicationRequestEntry = entries.find((entry) => entry.resource?.resourceType === "MedicationRequest" && getAdequationScore(entry) <= threshold);
1643
1705
  return medicationRequestEntry?.resource;
1644
1706
  }
1645
- const t$3 = globalThis, e$4 = t$3.ShadowRoot && (void 0 === t$3.ShadyCSS || t$3.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$2 = /* @__PURE__ */ Symbol(), o$5 = /* @__PURE__ */ new WeakMap();
1707
+ const t$3 = globalThis, e$4 = t$3.ShadowRoot && (void 0 === t$3.ShadyCSS || t$3.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$2 = /* @__PURE__ */ Symbol(), o$6 = /* @__PURE__ */ new WeakMap();
1646
1708
  let n$3 = class n {
1647
1709
  constructor(t2, e2, o2) {
1648
1710
  if (this._$cssResult$ = true, o2 !== s$2) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");
@@ -1653,7 +1715,7 @@ let n$3 = class n {
1653
1715
  const s2 = this.t;
1654
1716
  if (e$4 && void 0 === t2) {
1655
1717
  const e2 = void 0 !== s2 && 1 === s2.length;
1656
- e2 && (t2 = o$5.get(s2)), void 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), e2 && o$5.set(s2, t2));
1718
+ e2 && (t2 = o$6.get(s2)), void 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), e2 && o$6.set(s2, t2));
1657
1719
  }
1658
1720
  return t2;
1659
1721
  }
@@ -1679,7 +1741,7 @@ const r$4 = (t2) => new n$3("string" == typeof t2 ? t2 : t2 + "", void 0, s$2),
1679
1741
  for (const s2 of t3.cssRules) e2 += s2.cssText;
1680
1742
  return r$4(e2);
1681
1743
  })(t2) : t2;
1682
- const { is: i$3, defineProperty: e$3, getOwnPropertyDescriptor: h$1, getOwnPropertyNames: r$3, getOwnPropertySymbols: o$4, getPrototypeOf: n$2 } = Object, a$1 = globalThis, c$1 = a$1.trustedTypes, l$1 = c$1 ? c$1.emptyScript : "", p$1 = a$1.reactiveElementPolyfillSupport, d$1 = (t2, s2) => t2, u$1 = { toAttribute(t2, s2) {
1744
+ const { is: i$3, defineProperty: e$3, getOwnPropertyDescriptor: h$1, getOwnPropertyNames: r$3, getOwnPropertySymbols: o$5, getPrototypeOf: n$2 } = Object, a$1 = globalThis, c$1 = a$1.trustedTypes, l$1 = c$1 ? c$1.emptyScript : "", p$1 = a$1.reactiveElementPolyfillSupport, d$1 = (t2, s2) => t2, u$1 = { toAttribute(t2, s2) {
1683
1745
  switch (s2) {
1684
1746
  case Boolean:
1685
1747
  t2 = t2 ? l$1 : null;
@@ -1744,7 +1806,7 @@ let y$1 = class y extends HTMLElement {
1744
1806
  static finalize() {
1745
1807
  if (this.hasOwnProperty(d$1("finalized"))) return;
1746
1808
  if (this.finalized = true, this._$Ei(), this.hasOwnProperty(d$1("properties"))) {
1747
- const t3 = this.properties, s2 = [...r$3(t3), ...o$4(t3)];
1809
+ const t3 = this.properties, s2 = [...r$3(t3), ...o$5(t3)];
1748
1810
  for (const i3 of s2) this.createProperty(i3, t3[i3]);
1749
1811
  }
1750
1812
  const t2 = this[Symbol.metadata];
@@ -1891,7 +1953,7 @@ let y$1 = class y extends HTMLElement {
1891
1953
  }
1892
1954
  };
1893
1955
  y$1.elementStyles = [], y$1.shadowRootOptions = { mode: "open" }, y$1[d$1("elementProperties")] = /* @__PURE__ */ new Map(), y$1[d$1("finalized")] = /* @__PURE__ */ new Map(), p$1?.({ ReactiveElement: y$1 }), (a$1.reactiveElementVersions ??= []).push("2.1.2");
1894
- const t$2 = globalThis, i$2 = (t2) => t2, s$1 = t$2.trustedTypes, e$2 = s$1 ? s$1.createPolicy("lit-html", { createHTML: (t2) => t2 }) : void 0, h = "$lit$", o$3 = `lit$${Math.random().toFixed(9).slice(2)}$`, n$1 = "?" + o$3, r$2 = `<${n$1}>`, l = document, c = () => l.createComment(""), a = (t2) => null === t2 || "object" != typeof t2 && "function" != typeof t2, u = Array.isArray, d = (t2) => u(t2) || "function" == typeof t2?.[Symbol.iterator], f = "[ \n\f\r]", v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m = />/g, p = RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^
1956
+ const t$2 = globalThis, i$2 = (t2) => t2, s$1 = t$2.trustedTypes, e$2 = s$1 ? s$1.createPolicy("lit-html", { createHTML: (t2) => t2 }) : void 0, h = "$lit$", o$4 = `lit$${Math.random().toFixed(9).slice(2)}$`, n$1 = "?" + o$4, r$2 = `<${n$1}>`, l = document, c = () => l.createComment(""), a = (t2) => null === t2 || "object" != typeof t2 && "function" != typeof t2, u = Array.isArray, d = (t2) => u(t2) || "function" == typeof t2?.[Symbol.iterator], f = "[ \n\f\r]", v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m = />/g, p = RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^
1895
1957
  \f\r"'\`<>=]|("|')|))|$)`, "g"), g = /'/g, $ = /"/g, y2 = /^(?:script|style|textarea|title)$/i, x = (t2) => (i3, ...s2) => ({ _$litType$: t2, strings: i3, values: s2 }), b = x(1), E = /* @__PURE__ */ Symbol.for("lit-noChange"), A = /* @__PURE__ */ Symbol.for("lit-nothing"), C = /* @__PURE__ */ new WeakMap(), P = l.createTreeWalker(l, 129);
1896
1958
  function V(t2, i3) {
1897
1959
  if (!u(t2) || !t2.hasOwnProperty("raw")) throw Error("invalid template strings array");
@@ -1905,7 +1967,7 @@ const N = (t2, i3) => {
1905
1967
  let a2, u2, d2 = -1, f2 = 0;
1906
1968
  for (; f2 < s3.length && (c2.lastIndex = f2, u2 = c2.exec(s3), null !== u2); ) f2 = c2.lastIndex, c2 === v ? "!--" === u2[1] ? c2 = _ : void 0 !== u2[1] ? c2 = m : void 0 !== u2[2] ? (y2.test(u2[2]) && (n3 = RegExp("</" + u2[2], "g")), c2 = p) : void 0 !== u2[3] && (c2 = p) : c2 === p ? ">" === u2[0] ? (c2 = n3 ?? v, d2 = -1) : void 0 === u2[1] ? d2 = -2 : (d2 = c2.lastIndex - u2[2].length, a2 = u2[1], c2 = void 0 === u2[3] ? p : '"' === u2[3] ? $ : g) : c2 === $ || c2 === g ? c2 = p : c2 === _ || c2 === m ? c2 = v : (c2 = p, n3 = void 0);
1907
1969
  const x2 = c2 === p && t2[i4 + 1].startsWith("/>") ? " " : "";
1908
- l2 += c2 === v ? s3 + r$2 : d2 >= 0 ? (e2.push(a2), s3.slice(0, d2) + h + s3.slice(d2) + o$3 + x2) : s3 + o$3 + (-2 === d2 ? i4 : x2);
1970
+ l2 += c2 === v ? s3 + r$2 : d2 >= 0 ? (e2.push(a2), s3.slice(0, d2) + h + s3.slice(d2) + o$4 + x2) : s3 + o$4 + (-2 === d2 ? i4 : x2);
1909
1971
  }
1910
1972
  return [V(t2, l2 + (t2[s2] || "<?>") + (2 === i3 ? "</svg>" : 3 === i3 ? "</math>" : "")), e2];
1911
1973
  };
@@ -1922,11 +1984,11 @@ class S {
1922
1984
  for (; null !== (r2 = P.nextNode()) && d2.length < u2; ) {
1923
1985
  if (1 === r2.nodeType) {
1924
1986
  if (r2.hasAttributes()) for (const t3 of r2.getAttributeNames()) if (t3.endsWith(h)) {
1925
- const i4 = v2[a2++], s2 = r2.getAttribute(t3).split(o$3), e3 = /([.?@])?(.*)/.exec(i4);
1987
+ const i4 = v2[a2++], s2 = r2.getAttribute(t3).split(o$4), e3 = /([.?@])?(.*)/.exec(i4);
1926
1988
  d2.push({ type: 1, index: l2, name: e3[2], strings: s2, ctor: "." === e3[1] ? I : "?" === e3[1] ? L : "@" === e3[1] ? z : H }), r2.removeAttribute(t3);
1927
- } else t3.startsWith(o$3) && (d2.push({ type: 6, index: l2 }), r2.removeAttribute(t3));
1989
+ } else t3.startsWith(o$4) && (d2.push({ type: 6, index: l2 }), r2.removeAttribute(t3));
1928
1990
  if (y2.test(r2.tagName)) {
1929
- const t3 = r2.textContent.split(o$3), i4 = t3.length - 1;
1991
+ const t3 = r2.textContent.split(o$4), i4 = t3.length - 1;
1930
1992
  if (i4 > 0) {
1931
1993
  r2.textContent = s$1 ? s$1.emptyScript : "";
1932
1994
  for (let s2 = 0; s2 < i4; s2++) r2.append(t3[s2], c()), P.nextNode(), d2.push({ type: 2, index: ++l2 });
@@ -1936,7 +1998,7 @@ class S {
1936
1998
  } else if (8 === r2.nodeType) if (r2.data === n$1) d2.push({ type: 2, index: l2 });
1937
1999
  else {
1938
2000
  let t3 = -1;
1939
- for (; -1 !== (t3 = r2.data.indexOf(o$3, t3 + 1)); ) d2.push({ type: 7, index: l2 }), t3 += o$3.length - 1;
2001
+ for (; -1 !== (t3 = r2.data.indexOf(o$4, t3 + 1)); ) d2.push({ type: 7, index: l2 }), t3 += o$4.length - 1;
1940
2002
  }
1941
2003
  l2++;
1942
2004
  }
@@ -2139,15 +2201,15 @@ let i$1 = class i extends y$1 {
2139
2201
  }
2140
2202
  };
2141
2203
  i$1._$litElement$ = true, i$1["finalized"] = true, s.litElementHydrateSupport?.({ LitElement: i$1 });
2142
- const o$2 = s.litElementPolyfillSupport;
2143
- o$2?.({ LitElement: i$1 });
2204
+ const o$3 = s.litElementPolyfillSupport;
2205
+ o$3?.({ LitElement: i$1 });
2144
2206
  (s.litElementVersions ??= []).push("4.2.2");
2145
2207
  const t$1 = (t2) => (e2, o2) => {
2146
2208
  void 0 !== o2 ? o2.addInitializer(() => {
2147
2209
  customElements.define(t2, e2);
2148
2210
  }) : customElements.define(t2, e2);
2149
2211
  };
2150
- const o$1 = { attribute: true, type: String, converter: u$1, reflect: false, hasChanged: f$1 }, r$1 = (t2 = o$1, e2, r2) => {
2212
+ const o$2 = { attribute: true, type: String, converter: u$1, reflect: false, hasChanged: f$1 }, r$1 = (t2 = o$2, e2, r2) => {
2151
2213
  const { kind: n3, metadata: i3 } = r2;
2152
2214
  let s2 = globalThis.litPropertyMetadata.get(i3);
2153
2215
  if (void 0 === s2 && globalThis.litPropertyMetadata.set(i3, s2 = /* @__PURE__ */ new Map()), "setter" === n3 && ((t2 = Object.create(t2)).wrapped = true), s2.set(r2.name, t2), "accessor" === n3) {
@@ -2177,6 +2239,7 @@ function n2(t2) {
2177
2239
  function r(r2) {
2178
2240
  return n2({ ...r2, state: true, attribute: false });
2179
2241
  }
2242
+ const o$1 = (o2) => o2 ?? A;
2180
2243
  const t = { CHILD: 2 }, e$1 = (t2) => (...e2) => ({ _$litDirective$: t2, values: e2 });
2181
2244
  class i2 {
2182
2245
  constructor(t2) {
@@ -2305,15 +2368,17 @@ const EXCLUSION_GROUPS = [
2305
2368
  { key: "activeSubstances", idsField: "activeSubstanceIds", label: "Principes actifs" },
2306
2369
  { key: "notableExcipients", idsField: "notableExcipientIds", label: "Excipients à effet notoire" },
2307
2370
  { key: "excipients", idsField: "excipientIds", label: "Excipients" },
2308
- { key: "contraindications", idsField: "contraindicationIds", label: "Utilisation contre-indiquée" },
2309
- { key: "cautions", idsField: "cautionIds", label: "Utilisation déconseillée" },
2371
+ // Utilisation contre-indiquée + déconseillée : un seul groupe, le backend exclut un produit qui a
2372
+ // le mot-clé dans l'une ou l'autre liste.
2373
+ { key: "contraindications", idsField: "contraindicationIds", label: "Contre-indications" },
2310
2374
  { key: "precautions", idsField: "precautionIds", label: "Précaution d'emploi" },
2311
2375
  { key: "sideEffects", idsField: "sideEffectIds", label: "Effets indésirables" }
2312
2376
  ];
2313
2377
  const QUICK_FILTERS = [
2314
2378
  { key: "sameGalenicForm", label: "Même forme galénique" },
2315
2379
  { key: "sameDosage", label: "Même dosage" },
2316
- { key: "sameRouteOfAdministration", label: "Même voie d'administration" }
2380
+ { key: "sameRouteOfAdministration", label: "Même voie d'administration" },
2381
+ { key: "samePackaging", label: "Même conditionnement" }
2317
2382
  ];
2318
2383
  const INCLUDE_GROUPS = [
2319
2384
  { key: "activeSubstances", idsField: "activeSubstanceIds", label: "Principes actifs" },
@@ -2327,7 +2392,7 @@ const MATCH_REASON_VERB_BY_DIMENSION = {
2327
2392
  indication: "indiqué pour",
2328
2393
  activeSubstance: "contient"
2329
2394
  };
2330
- const therapeuticAlternativeCSS = '.claudebernard-therapeutic-alternative {\n font-family: var(--custom-font-family, "Ubuntu"), sans-serif;\n padding: 1rem;\n}\n.claudebernard-therapeutic-alternative-without-title {\n padding-top: 0;\n max-width: 70vw;\n width: 60vw;\n max-height: 70vh;\n}\n.claudebernard-therapeutic-alternative-title {\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--bcbd-gray-500-color, #64748B);\n margin-bottom: 0.375rem;\n display: block;\n}\n.claudebernard-therapeutic-alternative__source {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__source .claudebernard-therapeutic-alternative-title {\n margin-bottom: 0;\n}\n.claudebernard-therapeutic-alternative__source-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__disclaimer {\n margin-bottom: 1rem;\n}\n.claudebernard-therapeutic-alternative__loading {\n width: 100%;\n height: 30%;\n padding-top: 2rem;\n display: flex;\n justify-content: center;\n}\n.claudebernard-therapeutic-alternative__section {\n border: 1px solid var(--bcbd-border-gray-color, #CBD5E1);\n border-radius: 0.625rem;\n padding: 0.75rem 0.875rem;\n margin-bottom: 1rem;\n}\n.claudebernard-therapeutic-alternative__section-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n cursor: pointer;\n background: none;\n border: none;\n padding: 0;\n margin-bottom: 0.625rem;\n font-family: inherit;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__section-header span:first-child {\n font-size: 0.75rem;\n font-weight: 700;\n color: var(--bcbd-gray-500-color, #64748B);\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n.claudebernard-therapeutic-alternative__chevron {\n font-size: 0.75rem;\n color: var(--bcbd-gray-400-color, #94A3B8);\n transition: transform 0.15s ease-out;\n display: inline-flex;\n}\n.claudebernard-therapeutic-alternative__chevron--expanded {\n transform: rotate(180deg);\n}\n.claudebernard-therapeutic-alternative__pills {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__pill {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n border: 1px solid var(--bcbd-border-gray-color, #CBD5E1);\n background: #fff;\n color: var(--bcbd-gray-600-color, #475569);\n border-radius: 6.25rem;\n padding: 0.375rem 0.75rem;\n font-size: 0.8125rem;\n font-family: inherit;\n font-weight: 400;\n white-space: nowrap;\n cursor: pointer;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__pill--active {\n border-color: var(--bcbd-primary-color, #0C75AD);\n background: color-mix(in srgb, var(--bcbd-primary-color, #0C75AD) 10%, transparent);\n color: var(--bcbd-primary-color, #0C75AD);\n font-weight: 700;\n}\n.claudebernard-therapeutic-alternative__panel {\n border-top: 1px solid var(--bcbd-border-color-200, #E2E8F0);\n margin-top: 0.75rem;\n padding-top: 0.75rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__panel-rows {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n max-height: 16.25rem;\n overflow-y: auto;\n padding: 0.25rem 0.125rem;\n}\n.claudebernard-therapeutic-alternative__panel-empty {\n display: block;\n margin-top: 0.75rem;\n font-size: 0.875rem;\n color: var(--bcbd-gray-500-color, #64748B);\n}\n.claudebernard-therapeutic-alternative__panel-expand {\n align-self: flex-start;\n background: none;\n border: none;\n padding: 0.25rem 0 0;\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: 700;\n color: var(--bcbd-primary-color, #0C75AD);\n cursor: pointer;\n}\n.claudebernard-therapeutic-alternative__panel-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n border-radius: 0.375rem;\n --bcbd-checkbox-font-size: 0.875rem;\n --bcbd-checkbox-size: 0.9375rem;\n}\n.claudebernard-therapeutic-alternative__panel-row--pending {\n background: var(--bcbd-gray-100-color, #F1F5F9);\n padding: 0.25rem 0.375rem;\n margin: -0.25rem -0.375rem;\n}\n.claudebernard-therapeutic-alternative__panel-row--possessed {\n font-weight: 700;\n}\n.claudebernard-therapeutic-alternative__count-badge {\n font-size: 0.75rem;\n color: var(--bcbd-white-color, #fff);\n background: var(--bcbd-primary-color, #0C75AD);\n border-radius: 0.25rem;\n padding: 0.125rem 0.375rem;\n flex-shrink: 0;\n}\n.claudebernard-therapeutic-alternative__selected-chips {\n display: flex;\n gap: 0.375rem;\n flex-wrap: wrap;\n margin-top: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__results {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__results-header {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n margin-bottom: 0.625rem;\n}\n.claudebernard-therapeutic-alternative__results-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n background: color-mix(in srgb, var(--bcbd-primary-color, #0C75AD) 10%, transparent);\n color: var(--bcbd-primary-color, #0C75AD);\n border-radius: 6.25rem;\n padding: 0.3125rem 0.75rem;\n font-size: 0.8125rem;\n font-weight: 700;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__results-list {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__no-results {\n text-align: center;\n padding: 1.5rem 1rem;\n color: var(--bcbd-gray-500-color, #64748B);\n font-size: 0.875rem;\n}\n.claudebernard-therapeutic-alternative__no-medication {\n padding: 1.25rem;\n}';
2395
+ const therapeuticAlternativeCSS = '.claudebernard-therapeutic-alternative {\n font-family: var(--custom-font-family, "Ubuntu"), sans-serif;\n}\n.claudebernard-therapeutic-alternative__beta-badge {\n display: flex;\n justify-content: flex-start;\n line-height: 0;\n margin-bottom: 0.25rem;\n}\n.claudebernard-therapeutic-alternative__beta-badge bcbd-badge {\n display: inline-flex;\n padding: 0.1rem 0;\n border-radius: 50px;\n background-color: var(--bcbd-warning-color, #F59F00);\n}\n.claudebernard-therapeutic-alternative__source {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__source-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__disclaimer {\n margin-bottom: 1rem;\n}\n.claudebernard-therapeutic-alternative__loading {\n width: 100%;\n height: 30%;\n padding-top: 2rem;\n display: flex;\n justify-content: center;\n}\n.claudebernard-therapeutic-alternative__section {\n border: 1px solid var(--bcbd-border-gray-color, #CBD5E1);\n border-radius: 0.625rem;\n padding: 0.75rem 0.875rem;\n margin-bottom: 1rem;\n}\n.claudebernard-therapeutic-alternative__section-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n cursor: pointer;\n background: none;\n border: none;\n padding: 0;\n margin-bottom: 0.625rem;\n font-family: inherit;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__section-header span:first-child {\n font-size: 0.75rem;\n font-weight: 700;\n color: var(--bcbd-gray-500-color, #64748B);\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n.claudebernard-therapeutic-alternative__chevron {\n font-size: 0.75rem;\n color: var(--bcbd-gray-400-color, #94A3B8);\n transition: transform 0.15s ease-out;\n display: inline-flex;\n}\n.claudebernard-therapeutic-alternative__chevron--expanded {\n transform: rotate(180deg);\n}\n.claudebernard-therapeutic-alternative__pills {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__pill {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n border: 1px solid var(--bcbd-border-gray-color, #CBD5E1);\n background: #fff;\n color: var(--bcbd-gray-600-color, #475569);\n border-radius: 6.25rem;\n padding: 0.375rem 0.75rem;\n font-size: 0.8125rem;\n font-family: inherit;\n font-weight: 400;\n white-space: nowrap;\n cursor: pointer;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__pill--active {\n border-color: var(--bcbd-primary-color, #0C75AD);\n background: color-mix(in srgb, var(--bcbd-primary-color, #0C75AD) 10%, transparent);\n color: var(--bcbd-primary-color, #0C75AD);\n font-weight: 700;\n}\n.claudebernard-therapeutic-alternative__panel {\n border-top: 1px solid var(--bcbd-border-color-200, #E2E8F0);\n margin-top: 0.75rem;\n padding-top: 0.75rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__panel-rows {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n max-height: 16.25rem;\n overflow-y: auto;\n padding: 0.25rem 0.125rem;\n}\n.claudebernard-therapeutic-alternative__panel-empty {\n display: block;\n margin-top: 0.75rem;\n font-size: 0.875rem;\n color: var(--bcbd-gray-500-color, #64748B);\n}\n.claudebernard-therapeutic-alternative__panel-expand {\n align-self: flex-start;\n background: none;\n border: none;\n padding: 0.25rem 0 0;\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: 700;\n color: var(--bcbd-primary-color, #0C75AD);\n cursor: pointer;\n}\n.claudebernard-therapeutic-alternative__panel-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n border-radius: 0.375rem;\n --bcbd-checkbox-font-size: 0.875rem;\n --bcbd-checkbox-size: 0.9375rem;\n}\n.claudebernard-therapeutic-alternative__panel-row--pending {\n background: var(--bcbd-gray-100-color, #F1F5F9);\n padding: 0.25rem 0.375rem;\n margin: -0.25rem -0.375rem;\n}\n.claudebernard-therapeutic-alternative__panel-row--possessed {\n --bcbd-gray-900-color: var(--bcbd-primary-color, #0C75AD);\n}\n.claudebernard-therapeutic-alternative__count-badge {\n font-size: 0.75rem;\n color: var(--bcbd-white-color, #fff);\n background: var(--bcbd-primary-color, #0C75AD);\n border-radius: 0.25rem;\n padding: 0.125rem 0.375rem;\n flex-shrink: 0;\n}\n.claudebernard-therapeutic-alternative__selected-chips {\n display: flex;\n gap: 0.375rem;\n flex-wrap: wrap;\n margin-top: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__results {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__results-header {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n margin-bottom: 0.625rem;\n}\n.claudebernard-therapeutic-alternative__results-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n background: color-mix(in srgb, var(--bcbd-primary-color, #0C75AD) 10%, transparent);\n color: var(--bcbd-primary-color, #0C75AD);\n border-radius: 6.25rem;\n padding: 0.3125rem 0.75rem;\n font-size: 0.8125rem;\n font-weight: 700;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__results-list {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__no-results {\n text-align: center;\n padding: 1.5rem 1rem;\n color: var(--bcbd-gray-500-color, #64748B);\n font-size: 0.875rem;\n}\n.claudebernard-therapeutic-alternative__no-medication {\n padding: 1.25rem;\n}';
2331
2396
  function emptyScopeCriteria() {
2332
2397
  return { indicationIds: [], atcClassIds: [] };
2333
2398
  }
@@ -2340,13 +2405,12 @@ function emptyExcludeCriteria() {
2340
2405
  notableExcipientIds: [],
2341
2406
  excipientIds: [],
2342
2407
  contraindicationIds: [],
2343
- cautionIds: [],
2344
2408
  precautionIds: [],
2345
2409
  sideEffectIds: []
2346
2410
  };
2347
2411
  }
2348
2412
  function emptyQuickFilters() {
2349
- return { sameGalenicForm: false, sameDosage: false, sameRouteOfAdministration: false };
2413
+ return { sameGalenicForm: false, sameDosage: false, sameRouteOfAdministration: false, samePackaging: false };
2350
2414
  }
2351
2415
  function emptyScopeFacets() {
2352
2416
  return { indications: [], atcClasses: [] };
@@ -2360,7 +2424,6 @@ function emptyExcludeFacets() {
2360
2424
  notableExcipients: [],
2361
2425
  excipients: [],
2362
2426
  contraindications: [],
2363
- cautions: [],
2364
2427
  precautions: [],
2365
2428
  sideEffects: []
2366
2429
  };
@@ -2371,24 +2434,12 @@ function emptyScopeLabels() {
2371
2434
  function emptyIncludeLabels() {
2372
2435
  return { activeSubstances: {}, galenicForms: {}, dosages: {}, routesOfAdministration: {} };
2373
2436
  }
2374
- function labelsForIds(ids, values) {
2375
- const labelById = new Map(values.filter((v2) => v2.id).map((v2) => [v2.id, v2.label ?? v2.id]));
2376
- const result = {};
2377
- for (const id of ids) {
2378
- const label = labelById.get(id);
2379
- if (label) {
2380
- result[id] = label;
2381
- }
2382
- }
2383
- return result;
2384
- }
2385
2437
  function emptyExcludeLabels() {
2386
2438
  return {
2387
2439
  activeSubstances: {},
2388
2440
  notableExcipients: {},
2389
2441
  excipients: {},
2390
2442
  contraindications: {},
2391
- cautions: {},
2392
2443
  precautions: {},
2393
2444
  sideEffects: {}
2394
2445
  };
@@ -2406,10 +2457,11 @@ var __decorateClass = (decorators, target, key, kind) => {
2406
2457
  let TherapeuticAlternative = class extends i$1 {
2407
2458
  constructor() {
2408
2459
  super(...arguments);
2409
- this.hasTitle = false;
2410
2460
  this.medication = null;
2411
2461
  this.patientProfile = null;
2412
2462
  this.apiUrl = "https://api-dev.claudebernard.fr";
2463
+ this.showBetaBadge = true;
2464
+ this.isBeta = true;
2413
2465
  this.sourceProduct = null;
2414
2466
  this.results = [];
2415
2467
  this.totalElements = 0;
@@ -2439,7 +2491,6 @@ let TherapeuticAlternative = class extends i$1 {
2439
2491
  this.apiReplaceError = null;
2440
2492
  this.apiReplaceLoading = false;
2441
2493
  this.missingPrescriptionError = null;
2442
- this.hasAppliedInitialScope = false;
2443
2494
  this.requestSeq = 0;
2444
2495
  }
2445
2496
  get currentMedication() {
@@ -2452,6 +2503,16 @@ let TherapeuticAlternative = class extends i$1 {
2452
2503
  const bcbCoding = this.currentMedication?.code?.coding?.find((coding) => coding.system === "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code");
2453
2504
  return bcbCoding?.code ?? null;
2454
2505
  }
2506
+ get sourceProductLabel() {
2507
+ const title = getMedicationTitle(this.currentMedication);
2508
+ return title !== "" ? decodeHtml(title) : "ce produit";
2509
+ }
2510
+ // Same rule as bcb-cmp-ui's product sheet (isPara): product types 3 to 8 are parapharmacie, for
2511
+ // which there is no therapeutic alternative. Only known once the first response brings sourceProduct.
2512
+ get isParaSource() {
2513
+ const type = this.sourceProduct?.type;
2514
+ return type !== void 0 && type >= 3 && type <= 8;
2515
+ }
2455
2516
  updated(changedProperties) {
2456
2517
  if (changedProperties.has("apiUrl")) {
2457
2518
  client.setConfig({
@@ -2466,9 +2527,7 @@ let TherapeuticAlternative = class extends i$1 {
2466
2527
  }
2467
2528
  }
2468
2529
  // Shared by a medication change (updated() above) and the user-facing "Réinitialiser" button:
2469
- // both mean "start over from the initial state for this medication" -- clearing
2470
- // hasAppliedInitialScope so the next response re-derives and re-applies scope's own pre-check
2471
- // (see applyResponse) rather than leaving scope empty, which would NOT be the initial state.
2530
+ // both mean "start over from the initial state for this medication" (nothing selected).
2472
2531
  resetAllCriteria() {
2473
2532
  this.clearScheduledRefetch();
2474
2533
  this.scope = emptyScopeCriteria();
@@ -2483,7 +2542,6 @@ let TherapeuticAlternative = class extends i$1 {
2483
2542
  this.openIncludeGroup = null;
2484
2543
  this.expandFacetGroupKey = null;
2485
2544
  this.currentPage = 1;
2486
- this.hasAppliedInitialScope = false;
2487
2545
  this.fetchAlternatives();
2488
2546
  }
2489
2547
  disconnectedCallback() {
@@ -2507,7 +2565,7 @@ let TherapeuticAlternative = class extends i$1 {
2507
2565
  const anyScope = this.scope.indicationIds.length > 0 || this.scope.atcClassIds.length > 0;
2508
2566
  const anyInclude = Object.values(this.include).some((ids) => ids.length > 0);
2509
2567
  const anyExclude = Object.values(this.exclude).some((ids) => ids.length > 0);
2510
- const anyQuickFilter = this.quickFilters.sameGalenicForm || this.quickFilters.sameDosage || this.quickFilters.sameRouteOfAdministration;
2568
+ const anyQuickFilter = this.quickFilters.sameGalenicForm || this.quickFilters.sameDosage || this.quickFilters.sameRouteOfAdministration || this.quickFilters.samePackaging;
2511
2569
  return !anyScope && !anyInclude && !anyExclude && !anyQuickFilter;
2512
2570
  }
2513
2571
  fetchAlternatives() {
@@ -2528,6 +2586,7 @@ let TherapeuticAlternative = class extends i$1 {
2528
2586
  pageSize: numberOfResults,
2529
2587
  ...this.expandFacetGroupKey ? { expandFacetGroup: this.expandFacetGroupKey } : {}
2530
2588
  };
2589
+ setActiveContext(this.context);
2531
2590
  searchApi.searchAlternative({ body, path: { productId } }).then((response) => {
2532
2591
  if (seq !== this.requestSeq) {
2533
2592
  return;
@@ -2555,14 +2614,6 @@ let TherapeuticAlternative = class extends i$1 {
2555
2614
  this.excludeFacets = TherapeuticAlternative.buildExcludeFacets(data);
2556
2615
  this.quickFilterFacets = data.facets?.quickFilters ?? {};
2557
2616
  this.truncatedGroups = data.facets?.truncatedGroups ?? [];
2558
- if (!this.hasAppliedInitialScope) {
2559
- this.hasAppliedInitialScope = true;
2560
- this.scope = TherapeuticAlternative.deriveInitialScope(this.sourceProduct);
2561
- this.scopeLabels = {
2562
- indications: labelsForIds(this.scope.indicationIds, this.scopeFacets.indications),
2563
- atcClasses: labelsForIds(this.scope.atcClassIds, this.scopeFacets.atcClasses)
2564
- };
2565
- }
2566
2617
  }
2567
2618
  static buildScopeFacets(data) {
2568
2619
  return {
@@ -2588,21 +2639,19 @@ let TherapeuticAlternative = class extends i$1 {
2588
2639
  static codesOf(list) {
2589
2640
  return (list ?? []).map((item) => item.code).filter((code) => !!code);
2590
2641
  }
2591
- static deriveInitialScope(sourceProduct) {
2592
- return {
2593
- indicationIds: TherapeuticAlternative.codesOf(sourceProduct?.extendedProperties?.keywords?.indicationsKeywords),
2594
- // Contrairement à indications (une liste plate de valeurs propres au produit),
2595
- // sourceProduct.properties.atcClasses est un fil d'Ariane hiérarchique (classe > sous-classe
2596
- // > ... > code le plus spécifique, ex: M, M01, M01A, M01AX, M01AX02) — les mapper tous comme
2597
- // ids cochés n'a pas de sens (5 "sélections" pour UNE seule classification), et envoie en
2598
- // plus des codes ancêtres bien trop larges (ex: "M" = tout le système musculo-squelettique)
2599
- // dans la requête, invisibles dans l'UI puisque sans label correspondant dans
2600
- // scopeFacets.atcClasses. Rien n'est pré-coché pour ce groupe : le backend applique déjà son
2601
- // propre défaut sur atcClassIds vide (voir le commentaire plus haut sur fetchAlternatives).
2602
- atcClassIds: []
2603
- };
2642
+ // The ids to tint in a scope checklist panel (same highlight as the include/exclude panels, see
2643
+ // renderChecklistPanel's possessedIds param) -- the source product's own indications. They are
2644
+ // only highlighted, never pre-checked: an empty scope.indicationIds sends no indication
2645
+ // constraint, so ticking them would claim a filter that is not applied. The ATC group has none:
2646
+ // sourceProduct.properties.atcClasses is a whole breadcrumb (M > M01 > M01A > ...), not a flat
2647
+ // list of values the source "has".
2648
+ scopePossessedIds(key) {
2649
+ if (key !== "indications") {
2650
+ return /* @__PURE__ */ new Set();
2651
+ }
2652
+ return new Set(TherapeuticAlternative.codesOf(this.sourceProduct?.extendedProperties?.keywords?.indicationsKeywords));
2604
2653
  }
2605
- // The ids to bold in an include checklist panel (see renderChecklistPanel's possessedIds param)
2654
+ // The ids to tint in an include checklist panel (see renderChecklistPanel's possessedIds param)
2606
2655
  // -- what the SOURCE product itself already has for that dimension, not what's currently
2607
2656
  // selected. galenicForms/dosages are single-valued on the source (a product has exactly one), so
2608
2657
  // each contributes at most one id; activeSubstances/routesOfAdministration can be multi-valued.
@@ -2639,9 +2688,10 @@ let TherapeuticAlternative = class extends i$1 {
2639
2688
  case "excipients":
2640
2689
  return new Set(TherapeuticAlternative.codesOf(composition?.excipients));
2641
2690
  case "contraindications":
2642
- return new Set(TherapeuticAlternative.codesOf(keywords?.contraindicationsKeywords));
2643
- case "cautions":
2644
- return new Set(TherapeuticAlternative.codesOf(keywords?.contraindicationsNotRecommendedKeywords));
2691
+ return /* @__PURE__ */ new Set([
2692
+ ...TherapeuticAlternative.codesOf(keywords?.contraindicationsKeywords),
2693
+ ...TherapeuticAlternative.codesOf(keywords?.contraindicationsNotRecommendedKeywords)
2694
+ ]);
2645
2695
  case "precautions":
2646
2696
  return new Set(TherapeuticAlternative.codesOf(keywords?.precautionsForUseKeywords));
2647
2697
  case "sideEffects":
@@ -2776,6 +2826,7 @@ let TherapeuticAlternative = class extends i$1 {
2776
2826
  this.apiReplaceLoading = true;
2777
2827
  let newMedicationRequest;
2778
2828
  try {
2829
+ setActiveContext(this.context);
2779
2830
  const response = await prescriptionApi.getFhirPosologies({
2780
2831
  body: {
2781
2832
  patient: this.patientProfile ?? {},
@@ -2852,8 +2903,9 @@ let TherapeuticAlternative = class extends i$1 {
2852
2903
  pending ? `${PREFIX}-therapeutic-alternative__panel-row--pending` : "",
2853
2904
  possessedIds.has(item.id) ? `${PREFIX}-therapeutic-alternative__panel-row--possessed` : ""
2854
2905
  ].join(" ");
2906
+ const possessedTitle = possessedIds.has(item.id) ? `Valeur présente dans le produit d'origine (${this.sourceProductLabel})` : void 0;
2855
2907
  return b`
2856
- <div class=${rowClasses}>
2908
+ <div class=${rowClasses} title=${o$1(possessedTitle)}>
2857
2909
  <bcbd-checkbox
2858
2910
  id=${`${testIdPrefix}-${item.id}`}
2859
2911
  label=${showCode ? `${item.id} : ${item.label ?? ""}` : item.label ?? ""}
@@ -2998,7 +3050,8 @@ let TherapeuticAlternative = class extends i$1 {
2998
3050
  // distinct values) response back -- there's no third, even-larger tier to escalate to, so
2999
3051
  // showing the button again would just be a no-op click.
3000
3052
  openGroup.key === "atcClasses" && this.truncatedGroups.includes("atcClasses") && this.expandFacetGroupKey !== "atcClasses",
3001
- () => this.requestMoreFacetValues("atcClasses")
3053
+ () => this.requestMoreFacetValues("atcClasses"),
3054
+ this.scopePossessedIds(openGroup.key)
3002
3055
  ) : A}
3003
3056
  `;
3004
3057
  }
@@ -3225,25 +3278,37 @@ let TherapeuticAlternative = class extends i$1 {
3225
3278
  </bcbd-pagination>`}
3226
3279
  `;
3227
3280
  }
3228
- // The product name in the title replaces the "Produit sélectionné" line when has-title is on; without
3229
- // a title, that line (see render) is the only place the product name shows.
3230
- renderTitle(medicationTitle) {
3231
- return b`<span class="${PREFIX}-therapeutic-alternative-title" data-test-id="title">Alternative Thérapeutique pour ${medicationTitle !== "" ? decodeHtml(medicationTitle) : "ce produit"}</span>`;
3281
+ renderBetaBadge() {
3282
+ return this.isBeta && this.showBetaBadge ? b`
3283
+ <div class="${PREFIX}-therapeutic-alternative__beta-badge" data-test-id="beta-badge">
3284
+ <bcbd-tooltip content="Vous utilisez actuellement une version bêta" placement="bottom">
3285
+ <bcbd-badge label="bêta" background-color="warning" font-size="small" rounded></bcbd-badge>
3286
+ </bcbd-tooltip>
3287
+ </div>` : A;
3232
3288
  }
3233
3289
  render() {
3234
- const containerClasses = [
3235
- `${PREFIX}-therapeutic-alternative`,
3236
- this.hasTitle ? "" : `${PREFIX}-therapeutic-alternative-without-title`
3237
- ].join(" ");
3238
3290
  const medicationTitle = getMedicationTitle(this.currentMedication);
3291
+ if (this.currentMedication && this.isParaSource) {
3292
+ return b`
3293
+ <div class="${PREFIX}-therapeutic-alternative" data-test-id="therapeutic-alternative-container">
3294
+ ${this.renderBetaBadge()}
3295
+ <div class="${PREFIX}-therapeutic-alternative__disclaimer">
3296
+ <bcbd-alert title-message="Alternative thérapeutique indisponible"
3297
+ description-message="La recherche d'alternative n'est pas disponible pour les produits de parapharmacie."
3298
+ type="info" data-test-id="para-unavailable-alert">
3299
+ </bcbd-alert>
3300
+ </div>
3301
+ </div>
3302
+ `;
3303
+ }
3239
3304
  return b`
3240
- <div class=${containerClasses} data-test-id="therapeutic-alternative-container">
3305
+ <div class="${PREFIX}-therapeutic-alternative" data-test-id="therapeutic-alternative-container">
3306
+ ${this.renderBetaBadge()}
3241
3307
  ${this.currentMedication ? b`
3242
3308
  <div class="${PREFIX}-therapeutic-alternative__source mb-3" data-test-id="source-product">
3243
- ${this.hasTitle ? this.renderTitle(medicationTitle) : b`
3244
- <span data-test-id="source-product-label">
3245
- Produit sélectionné : <span class="text-primary">${medicationTitle !== "" ? decodeHtml(medicationTitle) : "Inconnu"}</span>
3246
- </span>`}
3309
+ <span data-test-id="source-product-label">
3310
+ Produit sélectionné : <span class="text-primary">${medicationTitle !== "" ? decodeHtml(medicationTitle) : "Inconnu"}</span>
3311
+ </span>
3247
3312
  <div class="${PREFIX}-therapeutic-alternative__source-actions">
3248
3313
  <bcbd-button size="small" background-color="secondary" outlined
3249
3314
  ?disabled=${this.hasNoSelectedCriteria()}
@@ -3297,7 +3362,6 @@ let TherapeuticAlternative = class extends i$1 {
3297
3362
  <div class="${PREFIX}-therapeutic-alternative__results" data-test-id="results-container">
3298
3363
  ${this.renderResults()}
3299
3364
  </div>` : b`
3300
- ${this.hasTitle ? this.renderTitle(medicationTitle) : ""}
3301
3365
  <div class="${PREFIX}-therapeutic-alternative__no-medication" data-test-id="no-medication-container">
3302
3366
  <bcbd-alert title-message="Pas de médicament sélectionné"
3303
3367
  description-message="Un problème est survenu au moment de sélectionner le produit"
@@ -3309,9 +3373,6 @@ let TherapeuticAlternative = class extends i$1 {
3309
3373
  }
3310
3374
  };
3311
3375
  TherapeuticAlternative.styles = i$4`${r$4([therapeuticAlternativeCSS, designSystemCSS])}`;
3312
- __decorateClass([
3313
- n2({ type: Boolean, attribute: "has-title" })
3314
- ], TherapeuticAlternative.prototype, "hasTitle", 2);
3315
3376
  __decorateClass([
3316
3377
  n2({ type: Object })
3317
3378
  ], TherapeuticAlternative.prototype, "medication", 2);
@@ -3324,6 +3385,19 @@ __decorateClass([
3324
3385
  __decorateClass([
3325
3386
  n2({ type: String, attribute: "api-key" })
3326
3387
  ], TherapeuticAlternative.prototype, "apiKey", 2);
3388
+ __decorateClass([
3389
+ n2({ type: Object })
3390
+ ], TherapeuticAlternative.prototype, "context", 2);
3391
+ __decorateClass([
3392
+ n2({
3393
+ type: Boolean,
3394
+ attribute: "show-beta-badge",
3395
+ converter: (value) => value !== "false"
3396
+ })
3397
+ ], TherapeuticAlternative.prototype, "showBetaBadge", 2);
3398
+ __decorateClass([
3399
+ r()
3400
+ ], TherapeuticAlternative.prototype, "isBeta", 2);
3327
3401
  __decorateClass([
3328
3402
  r()
3329
3403
  ], TherapeuticAlternative.prototype, "sourceProduct", 2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudebernard/therapeutic-alternative-element",
3
- "version": "2.3.0",
3
+ "version": "2.5.0-beta.1",
4
4
  "main": "dist/therapeutic-alternative.es.js",
5
5
  "module": "dist/therapeutic-alternative.es.js",
6
6
  "types": "dist/therapeutic-alternative.d.ts",
@@ -23,6 +23,7 @@
23
23
  "generate": "openapi-ts --file src/shared/api/search/search.openapi-config.ts && openapi-ts --file src/shared/api/info/info.openapi-config.ts && openapi-ts --file src/shared/api/prescription/prescription.openapi-config.ts",
24
24
  "storybook": "storybook dev -p 6006",
25
25
  "build-storybook": "storybook build",
26
+ "no-effect": "",
26
27
  "write-test": "npx playwright codegen",
27
28
  "test": "npx vitest",
28
29
  "e2e-test": "npx playwright test",
@@ -54,6 +55,7 @@
54
55
  "@storybook/addon-vitest": "^10.2.11",
55
56
  "@storybook/web-components": "^10.2.11",
56
57
  "@storybook/web-components-vite": "^10.2.11",
58
+ "@claudebernard/storybook-addon-json-editor-panel": "^0.3.0",
57
59
  "@claudebernard/storybook-addon-theme-switcher": "0.1.0",
58
60
  "@stylistic/eslint-plugin": "^5.9.0",
59
61
  "@types/fhir": "^0.0.41",
@@ -75,7 +77,7 @@
75
77
  "ts-node": "^10.9.2",
76
78
  "tslib": "^2.8.1",
77
79
  "typescript": "~5.9.3",
78
- "vite": "^7.3.1",
80
+ "vite": "^7.3.7",
79
81
  "vite-plugin-dts": "4.5.4",
80
82
  "vite-svg-sprite-wrapper": "1.5.0",
81
83
  "vitest": "^4.1.8"
@@ -98,53 +100,6 @@
98
100
  "customElements": "custom-elements.json",
99
101
  "publishConfig": {
100
102
  "access": "public"
101
- },
102
- "release": {
103
- "tagFormat": "v${version}",
104
- "branches": [
105
- "master"
106
- ],
107
- "plugins": [
108
- "@semantic-release/commit-analyzer",
109
- "@semantic-release/release-notes-generator",
110
- "@semantic-release/changelog",
111
- [
112
- "semantic-release-replace-plugin",
113
- {
114
- "replacements": [
115
- {
116
- "files": [
117
- "package.json"
118
- ],
119
- "from": "\"version\": \"\\d+\\.\\d+\\.\\d+\"",
120
- "to": "\"version\": \"${nextRelease.version}\"",
121
- "results": [
122
- {
123
- "file": "package.json",
124
- "hasChanged": true
125
- }
126
- ]
127
- }
128
- ]
129
- }
130
- ],
131
- [
132
- "@semantic-release/git",
133
- {
134
- "message": "chore(release): ${nextRelease.version} [skip ci on prod]",
135
- "assets": [
136
- "package.json",
137
- "CHANGELOG.md"
138
- ]
139
- }
140
- ],
141
- [
142
- "@semantic-release/exec",
143
- {
144
- "publishCmd": "sh -c 'set -e; V=$(echo ${nextRelease.version} | cut -d. -f1,2); sed -i \"s/^version: .*/version: $V.x/\" antora.yml; git add antora.yml; git commit -m \"docs(antora): set version to $V.x\"; git tag -f v$V-latest; git push https://gitlab-ci-token:$GITLAB_TOKEN@gitlab.cegedim-sante.com/resip/pim/web-components/therapeutic-alternative.git -f v$V-latest; git reset --hard HEAD~1'"
145
- }
146
- ],
147
- "@semantic-release/gitlab"
148
- ]
149
103
  }
150
104
  }
105
+