@youversion/platform-react-ui 1.14.3 → 1.15.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.
@@ -0,0 +1,8 @@
1
+ export type BibleCardProps = {
2
+ reference: string;
3
+ versionId: number;
4
+ background?: 'light' | 'dark';
5
+ showVersionPicker?: boolean;
6
+ };
7
+ export declare function BibleCard({ reference, versionId, background, showVersionPicker, }: BibleCardProps): React.ReactNode;
8
+ //# sourceMappingURL=bible-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bible-card.d.ts","sourceRoot":"","sources":["../../src/components/bible-card.tsx"],"names":[],"mappings":"AAQA,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AACF,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,SAAS,EACT,UAAU,EACV,iBAAyB,GAC1B,EAAE,cAAc,GAAG,KAAK,CAAC,SAAS,CAmElC"}
@@ -1 +1 @@
1
- {"version":3,"file":"bible-version-picker.d.ts","sourceRoot":"","sources":["../../src/components/bible-version-picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,2BAA2B,CAAC;AASxE,OAAO,EAEL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AASf,OAAO,EAA2B,cAAc,EAAE,MAAM,cAAc,CAAC;AAGvE,eAAO,MAAM,mBAAmB,+CAA+C,CAAC;AAqIhF,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3C,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,iBAAS,IAAI,CAAC,EACZ,SAAS,EAAE,mBAAmB,EAC9B,eAAe,EACf,UAAU,EACV,IAAY,EACZ,QAAQ,GACT,EAAE,SAAS,2CAoIX;AAED,MAAM,MAAM,8BAA8B,GAAG,IAAI,CAC/C,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,EAC3C,UAAU,CACX,GAAG;IACF,QAAQ,CAAC,EACL,KAAK,CAAC,SAAS,GACf,CAAC,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;CACtF,CAAC;AAEF,iBAAS,OAAO,CAAC,EAAE,OAAc,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,8BAA8B,2CAkBtF;AAED,iBAAS,OAAO,4CAsTf;AAED,eAAO,MAAM,kBAAkB;;;;CAAgD,CAAC;AAChF,MAAM,MAAM,2BAA2B,GAAG,SAAS,CAAC"}
1
+ {"version":3,"file":"bible-version-picker.d.ts","sourceRoot":"","sources":["../../src/components/bible-version-picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,2BAA2B,CAAC;AASxE,OAAO,EAEL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AASf,OAAO,EAA2B,cAAc,EAAE,MAAM,cAAc,CAAC;AAGvE,eAAO,MAAM,mBAAmB,+CAA+C,CAAC;AAqIhF,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3C,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,iBAAS,IAAI,CAAC,EACZ,SAAS,EAAE,mBAAmB,EAC9B,eAAe,EACf,UAAU,EACV,IAAY,EACZ,QAAQ,GACT,EAAE,SAAS,2CA+IX;AAED,MAAM,MAAM,8BAA8B,GAAG,IAAI,CAC/C,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,EAC3C,UAAU,CACX,GAAG;IACF,QAAQ,CAAC,EACL,KAAK,CAAC,SAAS,GACf,CAAC,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;CACtF,CAAC;AAEF,iBAAS,OAAO,CAAC,EAAE,OAAc,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,8BAA8B,2CAkBtF;AAED,iBAAS,OAAO,4CAsTf;AAED,eAAO,MAAM,kBAAkB;;;;CAAgD,CAAC;AAChF,MAAM,MAAM,2BAA2B,GAAG,SAAS,CAAC"}
@@ -1,8 +1,11 @@
1
- export type BibleWidgetViewProps = {
2
- reference: string;
3
- versionId: number;
4
- background?: 'light' | 'dark';
5
- showVersionPicker?: boolean;
6
- };
7
- export declare function BibleWidgetView({ reference, versionId, background, showVersionPicker, }: BibleWidgetViewProps): React.ReactNode;
1
+ import { BibleCard } from './bible-card';
2
+ import type { BibleCardProps } from './bible-card';
3
+ /**
4
+ * @deprecated Use `BibleCardProps` instead. This type alias will be removed in a future major version.
5
+ */
6
+ export type BibleWidgetViewProps = BibleCardProps;
7
+ /**
8
+ * @deprecated Use `BibleCard` instead. This component will be removed in a future major version.
9
+ */
10
+ export declare const BibleWidgetView: typeof BibleCard;
8
11
  //# sourceMappingURL=bible-widget-view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bible-widget-view.d.ts","sourceRoot":"","sources":["../../src/components/bible-widget-view.tsx"],"names":[],"mappings":"AAQA,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AACF,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,SAAS,EACT,UAAU,EACV,iBAAyB,GAC1B,EAAE,oBAAoB,GAAG,KAAK,CAAC,SAAS,CAmExC"}
1
+ {"version":3,"file":"bible-widget-view.d.ts","sourceRoot":"","sources":["../../src/components/bible-widget-view.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,eAAe,kBAAY,CAAC"}
@@ -4,5 +4,6 @@ export { BibleVersionPicker, type BibleVersionPickerRootProps, type BibleVersion
4
4
  export { YouVersionAuthButton, type YouVersionAuthButtonProps } from './YouVersionAuthButton';
5
5
  export { VerseOfTheDay, type VerseOfTheDayProps } from './verse-of-the-day';
6
6
  export { BibleTextView, type BibleTextViewProps } from './verse';
7
+ export { BibleCard, type BibleCardProps } from './bible-card';
7
8
  export { BibleWidgetView, type BibleWidgetViewProps } from './bible-widget-view';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EACL,kBAAkB,EAClB,KAAK,2BAA2B,EAChC,KAAK,8BAA8B,GACpC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,KAAK,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EACL,kBAAkB,EAClB,KAAK,2BAA2B,EAChC,KAAK,8BAA8B,GACpC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,KAAK,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"verse.d.ts","sourceRoot":"","sources":["../../src/components/verse.tsx"],"names":[],"mappings":"AAeA,OAAO,EAIL,KAAK,UAAU,EAGhB,MAAM,wBAAwB,CAAC;AAuPhC;;GAEG;AACH,KAAK,UAAU,GAAG;IAChB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK;IAChB;;;;;;;;OAQG;mCACwC,UAAU,KAAG,KAAK,CAAC,YAAY;;CA6E3E,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,qJAY3B,kBAAkB,KAAG,KAAK,CAAC,YA2D7B,CAAC"}
1
+ {"version":3,"file":"verse.d.ts","sourceRoot":"","sources":["../../src/components/verse.tsx"],"names":[],"mappings":"AAcA,OAAO,EAGL,KAAK,UAAU,EAEhB,MAAM,wBAAwB,CAAC;AA6JhC;;GAEG;AACH,KAAK,UAAU,GAAG;IAChB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK;IAChB;;;;;;;;OAQG;mCACwC,UAAU,KAAG,KAAK,CAAC,YAAY;;CAwE3E,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,qJAY3B,kBAAkB,KAAG,KAAK,CAAC,YA2D7B,CAAC"}
package/dist/index.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  ApiClient: () => ApiClient,
36
36
  BOOK_CANON: () => BOOK_CANON,
37
37
  BOOK_IDS: () => BOOK_IDS,
38
+ BibleCard: () => BibleCard,
38
39
  BibleChapterPicker: () => BibleChapterPicker,
39
40
  BibleClient: () => BibleClient,
40
41
  BibleReader: () => BibleReader,
@@ -15139,7 +15140,17 @@ function Root5({
15139
15140
  const [searchQuery, setSearchQuery] = (0, import_react2.useState)("");
15140
15141
  const [isLanguagesOpen, setIsLanguagesOpen] = (0, import_react2.useState)(false);
15141
15142
  const [recentVersions, setRecentVersions] = (0, import_react2.useState)(getRecentVersions);
15142
- const [isPopoverOpen, setIsPopoverOpen] = (0, import_react2.useState)(false);
15143
+ const [isPopoverOpen, setIsPopoverOpenRaw] = (0, import_react2.useState)(false);
15144
+ const setIsPopoverOpen = (0, import_react2.useCallback)(
15145
+ (open) => {
15146
+ setIsPopoverOpenRaw(open);
15147
+ if (!open) {
15148
+ setSearchQuery("");
15149
+ setIsLanguagesOpen(false);
15150
+ }
15151
+ },
15152
+ [setSearchQuery]
15153
+ );
15143
15154
  const addRecentVersion = (0, import_react2.useCallback)((version2) => {
15144
15155
  setRecentVersions((prev) => {
15145
15156
  const filtered = prev.filter((v) => v.id !== version2.id);
@@ -15569,13 +15580,24 @@ function PersonIcon(props) {
15569
15580
 
15570
15581
  // src/components/verse.tsx
15571
15582
  var import_platform_react_hooks3 = require("@youversion/platform-react-hooks");
15572
- var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"), 1);
15573
15583
  var import_react3 = require("react");
15574
15584
  var import_react_dom = require("react-dom");
15575
15585
 
15576
15586
  // src/lib/verse-html-utils.ts
15587
+ var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"), 1);
15577
15588
  var NON_BREAKING_SPACE = "\xA0";
15578
15589
  var LETTERS = "abcdefghijklmnopqrstuvwxyz";
15590
+ function getFootnoteMarker(index) {
15591
+ const base = LETTERS.length;
15592
+ if (base === 0) return String(index + 1);
15593
+ let value = index;
15594
+ let marker = "";
15595
+ do {
15596
+ marker = LETTERS[value % base] + marker;
15597
+ value = Math.floor(value / base) - 1;
15598
+ } while (value >= 0);
15599
+ return marker;
15600
+ }
15579
15601
  var INTER_FONT = '"Inter", sans-serif';
15580
15602
  var SOURCE_SERIF_FONT = '"Source Serif 4", serif';
15581
15603
  function wrapVerseContent(doc) {
@@ -15668,73 +15690,125 @@ function wrapVerseContent(doc) {
15668
15690
  const verseMarkers = Array.from(doc.querySelectorAll(".yv-v[v]"));
15669
15691
  verseMarkers.forEach(processVerseMarker);
15670
15692
  }
15693
+ var NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"»›]/;
15694
+ function buildVerseHtml(wrappers) {
15695
+ const parts = [];
15696
+ let noteIdx = 0;
15697
+ for (let i = 0; i < wrappers.length; i++) {
15698
+ if (i > 0) parts.push(" ");
15699
+ const clone2 = wrappers[i].cloneNode(true);
15700
+ const ownerDoc = wrappers[i].ownerDocument;
15701
+ clone2.querySelectorAll(".yv-h, .yv-vlbl").forEach((el) => el.remove());
15702
+ clone2.querySelectorAll(".yv-n.f").forEach((fn) => {
15703
+ const marker = ownerDoc.createElement("sup");
15704
+ marker.className = "yv:text-muted-foreground";
15705
+ marker.textContent = getFootnoteMarker(noteIdx++);
15706
+ fn.replaceWith(marker);
15707
+ });
15708
+ parts.push(clone2.innerHTML);
15709
+ }
15710
+ return parts.join("");
15711
+ }
15712
+ function replaceFootnotesWithAnchors(doc, footnotes) {
15713
+ for (const fn of footnotes) {
15714
+ const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
15715
+ if (!verseNum) continue;
15716
+ const prev = fn.previousSibling;
15717
+ const next = fn.nextSibling;
15718
+ const prevText = prev?.textContent ?? "";
15719
+ const nextText = next?.textContent ?? "";
15720
+ const prevNeedsSpace = prevText.length > 0 && !/\s$/.test(prevText);
15721
+ const nextNeedsSpace = nextText.length > 0 && NEEDS_SPACE_BEFORE.test(nextText);
15722
+ if (prevNeedsSpace && nextNeedsSpace && fn.parentNode) {
15723
+ fn.parentNode.insertBefore(doc.createTextNode(" "), fn);
15724
+ }
15725
+ const anchor = doc.createElement("span");
15726
+ anchor.setAttribute("data-verse-footnote", verseNum);
15727
+ fn.replaceWith(anchor);
15728
+ }
15729
+ }
15671
15730
  function extractNotesFromWrappedHtml(doc) {
15672
15731
  const footnotes = Array.from(doc.querySelectorAll(".yv-n.f"));
15673
15732
  if (!footnotes.length) return {};
15674
15733
  const footnotesByVerse = /* @__PURE__ */ new Map();
15675
- footnotes.forEach((fn) => {
15734
+ for (const fn of footnotes) {
15676
15735
  const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
15677
- if (verseNum) {
15678
- let arr = footnotesByVerse.get(verseNum);
15679
- if (!arr) {
15680
- arr = [];
15681
- footnotesByVerse.set(verseNum, arr);
15682
- }
15683
- arr.push(fn);
15736
+ if (!verseNum) continue;
15737
+ let arr = footnotesByVerse.get(verseNum);
15738
+ if (!arr) {
15739
+ arr = [];
15740
+ footnotesByVerse.set(verseNum, arr);
15684
15741
  }
15742
+ arr.push(fn);
15743
+ }
15744
+ const wrappersByVerse = /* @__PURE__ */ new Map();
15745
+ doc.querySelectorAll(".yv-v[v]").forEach((el) => {
15746
+ const verseNum = el.getAttribute("v");
15747
+ if (!verseNum) return;
15748
+ const arr = wrappersByVerse.get(verseNum);
15749
+ if (arr) arr.push(el);
15750
+ else wrappersByVerse.set(verseNum, [el]);
15685
15751
  });
15686
15752
  const notes = {};
15687
- const NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"»›]/;
15688
- footnotesByVerse.forEach((fns, verseNum) => {
15689
- const verseWrappers = Array.from(doc.querySelectorAll(`.yv-v[v="${verseNum}"]`));
15690
- let verseHtml = "";
15691
- let noteIdx = 0;
15692
- verseWrappers.forEach((wrapper, wrapperIdx) => {
15693
- if (wrapperIdx > 0) verseHtml += " ";
15694
- const walker = doc.createTreeWalker(wrapper, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
15695
- let lastWasFootnote = false;
15696
- while (walker.nextNode()) {
15697
- const node = walker.currentNode;
15698
- if (node instanceof Element) {
15699
- if (node.classList.contains("yv-n") && node.classList.contains("f")) {
15700
- verseHtml += `<sup class="yv:text-muted-foreground">${LETTERS[noteIdx++] || noteIdx}</sup>`;
15701
- lastWasFootnote = true;
15702
- }
15703
- } else if (node.nodeType === Node.TEXT_NODE) {
15704
- const parent = node.parentElement;
15705
- if (parent?.closest(".yv-n.f") || parent?.closest(".yv-h")) continue;
15706
- if (parent?.classList.contains("yv-vlbl")) continue;
15707
- let text = node.textContent || "";
15708
- if (lastWasFootnote && text && NEEDS_SPACE_BEFORE.test(text)) {
15709
- text = " " + text;
15710
- }
15711
- verseHtml += text;
15712
- lastWasFootnote = false;
15713
- }
15714
- }
15715
- });
15753
+ for (const [verseNum, fns] of footnotesByVerse) {
15716
15754
  notes[verseNum] = {
15717
- verseHtml,
15755
+ verseHtml: buildVerseHtml(wrappersByVerse.get(verseNum) ?? []),
15718
15756
  notes: fns.map((fn) => fn.innerHTML)
15719
15757
  };
15720
- const lastWrapper = verseWrappers[verseWrappers.length - 1];
15721
- if (lastWrapper?.parentNode) {
15722
- const placeholder = doc.createElement("span");
15723
- placeholder.setAttribute("data-verse-footnote", verseNum);
15724
- lastWrapper.parentNode.insertBefore(placeholder, lastWrapper.nextSibling);
15758
+ }
15759
+ replaceFootnotesWithAnchors(doc, footnotes);
15760
+ return notes;
15761
+ }
15762
+ function addNbspToVerseLabels(doc) {
15763
+ doc.querySelectorAll(".yv-vlbl").forEach((label) => {
15764
+ const text = label.textContent || "";
15765
+ if (!text.endsWith(NON_BREAKING_SPACE)) {
15766
+ label.textContent = text + NON_BREAKING_SPACE;
15725
15767
  }
15726
15768
  });
15727
- footnotes.forEach((fn) => {
15728
- const prev = fn.previousSibling;
15729
- const next = fn.nextSibling;
15730
- const prevNeedsSpace = prev?.nodeType === Node.TEXT_NODE && prev.textContent && !/\s$/.test(prev.textContent);
15731
- const nextNeedsSpace = next?.nodeType === Node.TEXT_NODE && next.textContent && NEEDS_SPACE_BEFORE.test(next.textContent);
15732
- if (prevNeedsSpace && nextNeedsSpace) {
15733
- fn.parentNode?.insertBefore(doc.createTextNode(" "), next);
15769
+ }
15770
+ function fixIrregularTables(doc) {
15771
+ doc.querySelectorAll("table").forEach((table) => {
15772
+ const rows = table.querySelectorAll("tr");
15773
+ if (rows.length === 0) return;
15774
+ let maxColumns = 0;
15775
+ rows.forEach((row) => {
15776
+ let count = 0;
15777
+ row.querySelectorAll("td, th").forEach((cell) => {
15778
+ count += cell instanceof HTMLTableCellElement ? parseInt(cell.getAttribute("colspan") || "1", 10) : 1;
15779
+ });
15780
+ maxColumns = Math.max(maxColumns, count);
15781
+ });
15782
+ if (maxColumns > 1) {
15783
+ rows.forEach((row) => {
15784
+ const cells = row.querySelectorAll("td, th");
15785
+ if (cells.length === 1 && cells[0] instanceof HTMLTableCellElement) {
15786
+ const existing = parseInt(cells[0].getAttribute("colspan") || "1", 10);
15787
+ if (existing < maxColumns) {
15788
+ cells[0].setAttribute("colspan", maxColumns.toString());
15789
+ }
15790
+ }
15791
+ });
15734
15792
  }
15735
- fn.remove();
15736
15793
  });
15737
- return notes;
15794
+ }
15795
+ var DOMPURIFY_CONFIG = {
15796
+ ALLOWED_ATTR: ["class", "style", "id", "v", "usfm"],
15797
+ ALLOW_DATA_ATTR: true
15798
+ };
15799
+ function transformBibleHtml(html) {
15800
+ if (typeof window === "undefined" || !("DOMParser" in window)) {
15801
+ return { html, notes: {} };
15802
+ }
15803
+ const doc = new DOMParser().parseFromString(
15804
+ import_isomorphic_dompurify.default.sanitize(html, DOMPURIFY_CONFIG),
15805
+ "text/html"
15806
+ );
15807
+ wrapVerseContent(doc);
15808
+ const notes = extractNotesFromWrappedHtml(doc);
15809
+ addNbspToVerseLabels(doc);
15810
+ fixIrregularTables(doc);
15811
+ return { html: doc.body.innerHTML, notes };
15738
15812
  }
15739
15813
 
15740
15814
  // src/components/icons/footnote.tsx
@@ -15795,20 +15869,23 @@ var VerseFootnoteButton = (0, import_react3.memo)(function VerseFootnoteButton2(
15795
15869
  dangerouslySetInnerHTML: { __html: verseNotes.verseHtml }
15796
15870
  }
15797
15871
  ),
15798
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("ul", { className: "yv:list-none yv:p-0 yv:m-0 yv:space-y-1", children: verseNotes.notes.map((note, index) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
15799
- "li",
15800
- {
15801
- className: "yv:flex yv:gap-2 yv:text-xs yv:border-b yv:border-border yv:py-2",
15802
- children: [
15803
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "", children: [
15804
- LETTERS[index] || index + 1,
15805
- "."
15806
- ] }),
15807
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { dangerouslySetInnerHTML: { __html: note } })
15808
- ]
15809
- },
15810
- LETTERS[index]
15811
- )) })
15872
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("ul", { className: "yv:list-none yv:p-0 yv:m-0 yv:space-y-1", children: verseNotes.notes.map((note, index) => {
15873
+ const marker = getFootnoteMarker(index);
15874
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
15875
+ "li",
15876
+ {
15877
+ className: "yv:flex yv:gap-2 yv:text-xs yv:border-b yv:border-border yv:py-2",
15878
+ children: [
15879
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "", children: [
15880
+ marker,
15881
+ "."
15882
+ ] }),
15883
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { dangerouslySetInnerHTML: { __html: note } })
15884
+ ]
15885
+ },
15886
+ marker
15887
+ );
15888
+ }) })
15812
15889
  ] })
15813
15890
  }
15814
15891
  )
@@ -15825,57 +15902,39 @@ function BibleTextHtml({
15825
15902
  highlightedVerses = {}
15826
15903
  }) {
15827
15904
  const contentRef = (0, import_react3.useRef)(null);
15828
- const [placeholders, setPlaceholders] = (0, import_react3.useState)(/* @__PURE__ */ new Map());
15905
+ const [placeholders, setPlaceholders] = (0, import_react3.useState)([]);
15829
15906
  const providerTheme = (0, import_platform_react_hooks3.useTheme)();
15830
15907
  const currentTheme = theme || providerTheme;
15831
15908
  (0, import_react3.useLayoutEffect)(() => {
15832
15909
  if (!contentRef.current) return;
15833
15910
  contentRef.current.innerHTML = html;
15834
- const map2 = /* @__PURE__ */ new Map();
15835
- Object.keys(notes).forEach((verseNum) => {
15836
- const el = contentRef.current?.querySelector(`[data-verse-footnote="${verseNum}"]`);
15837
- if (el) map2.set(verseNum, el);
15911
+ const anchors = contentRef.current.querySelectorAll("[data-verse-footnote]");
15912
+ const result = [];
15913
+ anchors.forEach((el) => {
15914
+ const verseNum = el.getAttribute("data-verse-footnote");
15915
+ if (verseNum) result.push({ verseNum, el });
15838
15916
  });
15839
- setPlaceholders(map2);
15840
- }, [html, notes]);
15917
+ setPlaceholders(result);
15918
+ }, [html]);
15841
15919
  (0, import_react3.useLayoutEffect)(() => {
15842
15920
  if (!contentRef.current) return;
15843
- const verseElements = contentRef.current.querySelectorAll(".yv-v[v]");
15844
- verseElements.forEach((el) => {
15921
+ contentRef.current.querySelectorAll(".yv-v[v]").forEach((el) => {
15845
15922
  const verseNum = parseInt(el.getAttribute("v") || "0", 10);
15846
- if (selectedVerses.includes(verseNum)) {
15847
- el.classList.add("yv-v-selected");
15848
- } else {
15849
- el.classList.remove("yv-v-selected");
15850
- }
15851
- if (highlightedVerses[verseNum]) {
15852
- el.classList.add("yv-v-highlighted");
15853
- } else {
15854
- el.classList.remove("yv-v-highlighted");
15855
- }
15923
+ el.classList.toggle("yv-v-selected", selectedVerses.includes(verseNum));
15924
+ el.classList.toggle("yv-v-highlighted", !!highlightedVerses[verseNum]);
15856
15925
  });
15857
15926
  }, [html, selectedVerses, highlightedVerses]);
15858
- const selectedVersesRef = (0, import_react3.useRef)(selectedVerses);
15859
- selectedVersesRef.current = selectedVerses;
15860
- (0, import_react3.useLayoutEffect)(() => {
15861
- const element = contentRef.current;
15862
- if (!element || !onVerseSelect) return;
15863
- const handleClick = (e) => {
15864
- const target = e.target;
15865
- const verseEl = target.closest(".yv-v[v]");
15866
- if (!verseEl) return;
15867
- const verseNum = parseInt(verseEl.getAttribute("v") || "0", 10);
15868
- if (verseNum === 0) return;
15869
- const current = selectedVersesRef.current;
15870
- const newSelected = current.includes(verseNum) ? current.filter((v) => v !== verseNum) : [...current, verseNum].sort((a, b) => a - b);
15871
- onVerseSelect(newSelected);
15872
- };
15873
- element.addEventListener("click", handleClick);
15874
- return () => element.removeEventListener("click", handleClick);
15875
- }, [onVerseSelect]);
15927
+ const handleClick = onVerseSelect ? (e) => {
15928
+ const verseEl = e.target.closest(".yv-v[v]");
15929
+ if (!verseEl) return;
15930
+ const verseNum = parseInt(verseEl.getAttribute("v") || "0", 10);
15931
+ if (!verseNum) return;
15932
+ const newSelected = selectedVerses.includes(verseNum) ? selectedVerses.filter((v) => v !== verseNum) : [...selectedVerses, verseNum].sort((a, b) => a - b);
15933
+ onVerseSelect(newSelected);
15934
+ } : void 0;
15876
15935
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
15877
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { ref: contentRef }),
15878
- Array.from(placeholders.entries()).map(([verseNum, el]) => {
15936
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { ref: contentRef, onClick: handleClick }),
15937
+ placeholders.map(({ verseNum, el }, index) => {
15879
15938
  const verseNotes = notes[verseNum];
15880
15939
  if (!verseNotes) return null;
15881
15940
  return (0, import_react_dom.createPortal)(
@@ -15889,68 +15948,12 @@ function BibleTextHtml({
15889
15948
  theme: currentTheme
15890
15949
  }
15891
15950
  ),
15892
- el
15951
+ el,
15952
+ `${verseNum}-${index}`
15893
15953
  );
15894
15954
  })
15895
15955
  ] });
15896
15956
  }
15897
- var DOMPURIFY_CONFIG = {
15898
- ALLOWED_ATTR: ["class", "style", "id", "v", "usfm"],
15899
- ALLOW_DATA_ATTR: true
15900
- };
15901
- function yvDomTransformer(html, extractNotes = false) {
15902
- if (typeof window === "undefined" || !("DOMParser" in window)) {
15903
- return { html, notes: {} };
15904
- }
15905
- const doc = new DOMParser().parseFromString(
15906
- import_isomorphic_dompurify.default.sanitize(html, DOMPURIFY_CONFIG),
15907
- "text/html"
15908
- );
15909
- wrapVerseContent(doc);
15910
- const extractedNotes = extractNotes ? extractNotesFromWrappedHtml(doc) : {};
15911
- const verseLabels = doc.querySelectorAll(".yv-vlbl");
15912
- verseLabels.forEach((label) => {
15913
- const text = label.textContent || "";
15914
- if (!text.endsWith(NON_BREAKING_SPACE)) {
15915
- label.textContent = text + NON_BREAKING_SPACE;
15916
- }
15917
- });
15918
- const tables = doc.querySelectorAll("table");
15919
- tables.forEach((table) => {
15920
- const rows = table.querySelectorAll("tr");
15921
- if (rows.length === 0) return;
15922
- let maxColumns = 0;
15923
- rows.forEach((row) => {
15924
- const cells = row.querySelectorAll("td, th");
15925
- let rowColumnCount = 0;
15926
- cells.forEach((cell) => {
15927
- if (cell instanceof HTMLTableCellElement) {
15928
- const colspan = parseInt(cell.getAttribute("colspan") || "1", 10);
15929
- rowColumnCount += colspan;
15930
- } else {
15931
- rowColumnCount += 1;
15932
- }
15933
- });
15934
- maxColumns = Math.max(maxColumns, rowColumnCount);
15935
- });
15936
- if (maxColumns > 1) {
15937
- rows.forEach((row) => {
15938
- const cells = row.querySelectorAll("td, th");
15939
- if (cells.length === 1) {
15940
- const cell = cells[0];
15941
- if (cell instanceof HTMLTableCellElement) {
15942
- const existingColspan = parseInt(cell.getAttribute("colspan") || "1", 10);
15943
- if (existingColspan < maxColumns) {
15944
- cell.setAttribute("colspan", maxColumns.toString());
15945
- }
15946
- }
15947
- }
15948
- });
15949
- }
15950
- });
15951
- const modifiedHtml = doc.body.innerHTML;
15952
- return { html: modifiedHtml, notes: extractedNotes };
15953
- }
15954
15957
  var Verse = {
15955
15958
  /**
15956
15959
  * Renders a single verse with superscript number and text.
@@ -15991,12 +15994,9 @@ var Verse = {
15991
15994
  onVerseSelect,
15992
15995
  highlightedVerses
15993
15996
  }, ref) => {
15994
- const [transformedData, setTransformedData] = (0, import_react3.useState)({ html, notes: {} });
15997
+ const transformedData = (0, import_react3.useMemo)(() => transformBibleHtml(html), [html]);
15995
15998
  const providerTheme = (0, import_platform_react_hooks3.useTheme)();
15996
15999
  const currentTheme = theme || providerTheme;
15997
- (0, import_react3.useEffect)(() => {
15998
- setTransformedData(yvDomTransformer(html, true));
15999
- }, [html]);
16000
16000
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
16001
16001
  "section",
16002
16002
  {
@@ -16260,7 +16260,7 @@ function Content5() {
16260
16260
  function UserMenu() {
16261
16261
  const { auth, signIn, signOut, userInfo } = (0, import_platform_react_hooks4.useYVAuth)();
16262
16262
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Popover, { children: [
16263
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverTrigger, { "data-testid": "user-menu-trigger", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { size: "icon", variant: "secondary", children: auth.isAuthenticated && userInfo?.avatarUrlFormat ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
16263
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverTrigger, { asChild: true, "data-testid": "user-menu-trigger", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { size: "icon", variant: "secondary", children: auth.isAuthenticated && userInfo?.avatarUrlFormat ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
16264
16264
  "img",
16265
16265
  {
16266
16266
  src: userInfo.getAvatarUrl(32, 32)?.toString(),
@@ -16353,7 +16353,7 @@ function Toolbar({ border = "top" }) {
16353
16353
  )
16354
16354
  ] }),
16355
16355
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Popover, { children: [
16356
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverTrigger, { "aria-label": "Settings", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { size: "icon", variant: "secondary", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GearIcon, { className: "yv:text-foreground" }) }) }),
16356
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverTrigger, { asChild: true, "aria-label": "Settings", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { size: "icon", variant: "secondary", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GearIcon, { className: "yv:text-foreground" }) }) }),
16357
16357
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverContent, { sideOffset: 16, heading: "Reader Settings", theme: background, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "yv:flex yv:flex-col yv:gap-4 yv:p-4", children: [
16358
16358
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "yv:grid yv:grid-cols-2", children: [
16359
16359
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
@@ -16972,11 +16972,11 @@ function VerseOfTheDay({
16972
16972
  );
16973
16973
  }
16974
16974
 
16975
- // src/components/bible-widget-view.tsx
16975
+ // src/components/bible-card.tsx
16976
16976
  var import_platform_react_hooks7 = require("@youversion/platform-react-hooks");
16977
16977
  var import_react8 = require("react");
16978
16978
  var import_jsx_runtime29 = require("react/jsx-runtime");
16979
- function BibleWidgetView({
16979
+ function BibleCard({
16980
16980
  reference,
16981
16981
  versionId,
16982
16982
  background,
@@ -17038,7 +17038,7 @@ function BibleWidgetView({
17038
17038
  }
17039
17039
  ),
17040
17040
  /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "yv:grid yv:grid-cols-[1fr_auto] yv:gap-4 yv:items-center yv:mt-4", children: [
17041
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "yv:text-balance yv:text-muted-foreground yv:justify-self-start yv:font-bold yv:text-[0.5rem]", children: version2?.copyright }),
17041
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "yv:text-balance yv:text-muted-foreground yv:justify-self-start yv:font-bold yv:text-[0.5rem]", children: version2?.copyright }),
17042
17042
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "yv:justify-self-end", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(SvgComponent, { fontSize: 12 }) })
17043
17043
  ] })
17044
17044
  ]
@@ -17046,6 +17046,9 @@ function BibleWidgetView({
17046
17046
  );
17047
17047
  }
17048
17048
 
17049
+ // src/components/bible-widget-view.tsx
17050
+ var BibleWidgetView = BibleCard;
17051
+
17049
17052
  // src/index.ts
17050
17053
  var import_platform_react_hooks8 = require("@youversion/platform-react-hooks");
17051
17054
  injectStyles();
@@ -17054,6 +17057,7 @@ injectStyles();
17054
17057
  ApiClient,
17055
17058
  BOOK_CANON,
17056
17059
  BOOK_IDS,
17060
+ BibleCard,
17057
17061
  BibleChapterPicker,
17058
17062
  BibleClient,
17059
17063
  BibleReader,
package/dist/index.js CHANGED
@@ -14726,9 +14726,9 @@ import {
14726
14726
  import {
14727
14727
  createContext as createContext3,
14728
14728
  useContext as useContext3,
14729
- useEffect as useEffect4,
14729
+ useEffect as useEffect3,
14730
14730
  useLayoutEffect as useLayoutEffect2,
14731
- useMemo as useMemo3,
14731
+ useMemo as useMemo4,
14732
14732
  useState as useState4
14733
14733
  } from "react";
14734
14734
 
@@ -15121,7 +15121,17 @@ function Root5({
15121
15121
  const [searchQuery, setSearchQuery] = useState2("");
15122
15122
  const [isLanguagesOpen, setIsLanguagesOpen] = useState2(false);
15123
15123
  const [recentVersions, setRecentVersions] = useState2(getRecentVersions);
15124
- const [isPopoverOpen, setIsPopoverOpen] = useState2(false);
15124
+ const [isPopoverOpen, setIsPopoverOpenRaw] = useState2(false);
15125
+ const setIsPopoverOpen = useCallback(
15126
+ (open) => {
15127
+ setIsPopoverOpenRaw(open);
15128
+ if (!open) {
15129
+ setSearchQuery("");
15130
+ setIsLanguagesOpen(false);
15131
+ }
15132
+ },
15133
+ [setSearchQuery]
15134
+ );
15125
15135
  const addRecentVersion = useCallback((version2) => {
15126
15136
  setRecentVersions((prev) => {
15127
15137
  const filtered = prev.filter((v) => v.id !== version2.id);
@@ -15551,11 +15561,10 @@ function PersonIcon(props) {
15551
15561
 
15552
15562
  // src/components/verse.tsx
15553
15563
  import { usePassage, useTheme as useTheme3 } from "@youversion/platform-react-hooks";
15554
- import DOMPurify from "isomorphic-dompurify";
15555
15564
  import {
15556
15565
  forwardRef as forwardRef2,
15557
15566
  memo,
15558
- useEffect as useEffect3,
15567
+ useMemo as useMemo3,
15559
15568
  useLayoutEffect,
15560
15569
  useRef as useRef3,
15561
15570
  useState as useState3
@@ -15563,8 +15572,20 @@ import {
15563
15572
  import { createPortal } from "react-dom";
15564
15573
 
15565
15574
  // src/lib/verse-html-utils.ts
15575
+ import DOMPurify from "isomorphic-dompurify";
15566
15576
  var NON_BREAKING_SPACE = "\xA0";
15567
15577
  var LETTERS = "abcdefghijklmnopqrstuvwxyz";
15578
+ function getFootnoteMarker(index) {
15579
+ const base = LETTERS.length;
15580
+ if (base === 0) return String(index + 1);
15581
+ let value = index;
15582
+ let marker = "";
15583
+ do {
15584
+ marker = LETTERS[value % base] + marker;
15585
+ value = Math.floor(value / base) - 1;
15586
+ } while (value >= 0);
15587
+ return marker;
15588
+ }
15568
15589
  var INTER_FONT = '"Inter", sans-serif';
15569
15590
  var SOURCE_SERIF_FONT = '"Source Serif 4", serif';
15570
15591
  function wrapVerseContent(doc) {
@@ -15657,73 +15678,125 @@ function wrapVerseContent(doc) {
15657
15678
  const verseMarkers = Array.from(doc.querySelectorAll(".yv-v[v]"));
15658
15679
  verseMarkers.forEach(processVerseMarker);
15659
15680
  }
15681
+ var NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"»›]/;
15682
+ function buildVerseHtml(wrappers) {
15683
+ const parts = [];
15684
+ let noteIdx = 0;
15685
+ for (let i = 0; i < wrappers.length; i++) {
15686
+ if (i > 0) parts.push(" ");
15687
+ const clone2 = wrappers[i].cloneNode(true);
15688
+ const ownerDoc = wrappers[i].ownerDocument;
15689
+ clone2.querySelectorAll(".yv-h, .yv-vlbl").forEach((el) => el.remove());
15690
+ clone2.querySelectorAll(".yv-n.f").forEach((fn) => {
15691
+ const marker = ownerDoc.createElement("sup");
15692
+ marker.className = "yv:text-muted-foreground";
15693
+ marker.textContent = getFootnoteMarker(noteIdx++);
15694
+ fn.replaceWith(marker);
15695
+ });
15696
+ parts.push(clone2.innerHTML);
15697
+ }
15698
+ return parts.join("");
15699
+ }
15700
+ function replaceFootnotesWithAnchors(doc, footnotes) {
15701
+ for (const fn of footnotes) {
15702
+ const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
15703
+ if (!verseNum) continue;
15704
+ const prev = fn.previousSibling;
15705
+ const next = fn.nextSibling;
15706
+ const prevText = prev?.textContent ?? "";
15707
+ const nextText = next?.textContent ?? "";
15708
+ const prevNeedsSpace = prevText.length > 0 && !/\s$/.test(prevText);
15709
+ const nextNeedsSpace = nextText.length > 0 && NEEDS_SPACE_BEFORE.test(nextText);
15710
+ if (prevNeedsSpace && nextNeedsSpace && fn.parentNode) {
15711
+ fn.parentNode.insertBefore(doc.createTextNode(" "), fn);
15712
+ }
15713
+ const anchor = doc.createElement("span");
15714
+ anchor.setAttribute("data-verse-footnote", verseNum);
15715
+ fn.replaceWith(anchor);
15716
+ }
15717
+ }
15660
15718
  function extractNotesFromWrappedHtml(doc) {
15661
15719
  const footnotes = Array.from(doc.querySelectorAll(".yv-n.f"));
15662
15720
  if (!footnotes.length) return {};
15663
15721
  const footnotesByVerse = /* @__PURE__ */ new Map();
15664
- footnotes.forEach((fn) => {
15722
+ for (const fn of footnotes) {
15665
15723
  const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
15666
- if (verseNum) {
15667
- let arr = footnotesByVerse.get(verseNum);
15668
- if (!arr) {
15669
- arr = [];
15670
- footnotesByVerse.set(verseNum, arr);
15671
- }
15672
- arr.push(fn);
15724
+ if (!verseNum) continue;
15725
+ let arr = footnotesByVerse.get(verseNum);
15726
+ if (!arr) {
15727
+ arr = [];
15728
+ footnotesByVerse.set(verseNum, arr);
15673
15729
  }
15730
+ arr.push(fn);
15731
+ }
15732
+ const wrappersByVerse = /* @__PURE__ */ new Map();
15733
+ doc.querySelectorAll(".yv-v[v]").forEach((el) => {
15734
+ const verseNum = el.getAttribute("v");
15735
+ if (!verseNum) return;
15736
+ const arr = wrappersByVerse.get(verseNum);
15737
+ if (arr) arr.push(el);
15738
+ else wrappersByVerse.set(verseNum, [el]);
15674
15739
  });
15675
15740
  const notes = {};
15676
- const NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"»›]/;
15677
- footnotesByVerse.forEach((fns, verseNum) => {
15678
- const verseWrappers = Array.from(doc.querySelectorAll(`.yv-v[v="${verseNum}"]`));
15679
- let verseHtml = "";
15680
- let noteIdx = 0;
15681
- verseWrappers.forEach((wrapper, wrapperIdx) => {
15682
- if (wrapperIdx > 0) verseHtml += " ";
15683
- const walker = doc.createTreeWalker(wrapper, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
15684
- let lastWasFootnote = false;
15685
- while (walker.nextNode()) {
15686
- const node = walker.currentNode;
15687
- if (node instanceof Element) {
15688
- if (node.classList.contains("yv-n") && node.classList.contains("f")) {
15689
- verseHtml += `<sup class="yv:text-muted-foreground">${LETTERS[noteIdx++] || noteIdx}</sup>`;
15690
- lastWasFootnote = true;
15691
- }
15692
- } else if (node.nodeType === Node.TEXT_NODE) {
15693
- const parent = node.parentElement;
15694
- if (parent?.closest(".yv-n.f") || parent?.closest(".yv-h")) continue;
15695
- if (parent?.classList.contains("yv-vlbl")) continue;
15696
- let text = node.textContent || "";
15697
- if (lastWasFootnote && text && NEEDS_SPACE_BEFORE.test(text)) {
15698
- text = " " + text;
15699
- }
15700
- verseHtml += text;
15701
- lastWasFootnote = false;
15702
- }
15703
- }
15704
- });
15741
+ for (const [verseNum, fns] of footnotesByVerse) {
15705
15742
  notes[verseNum] = {
15706
- verseHtml,
15743
+ verseHtml: buildVerseHtml(wrappersByVerse.get(verseNum) ?? []),
15707
15744
  notes: fns.map((fn) => fn.innerHTML)
15708
15745
  };
15709
- const lastWrapper = verseWrappers[verseWrappers.length - 1];
15710
- if (lastWrapper?.parentNode) {
15711
- const placeholder = doc.createElement("span");
15712
- placeholder.setAttribute("data-verse-footnote", verseNum);
15713
- lastWrapper.parentNode.insertBefore(placeholder, lastWrapper.nextSibling);
15746
+ }
15747
+ replaceFootnotesWithAnchors(doc, footnotes);
15748
+ return notes;
15749
+ }
15750
+ function addNbspToVerseLabels(doc) {
15751
+ doc.querySelectorAll(".yv-vlbl").forEach((label) => {
15752
+ const text = label.textContent || "";
15753
+ if (!text.endsWith(NON_BREAKING_SPACE)) {
15754
+ label.textContent = text + NON_BREAKING_SPACE;
15714
15755
  }
15715
15756
  });
15716
- footnotes.forEach((fn) => {
15717
- const prev = fn.previousSibling;
15718
- const next = fn.nextSibling;
15719
- const prevNeedsSpace = prev?.nodeType === Node.TEXT_NODE && prev.textContent && !/\s$/.test(prev.textContent);
15720
- const nextNeedsSpace = next?.nodeType === Node.TEXT_NODE && next.textContent && NEEDS_SPACE_BEFORE.test(next.textContent);
15721
- if (prevNeedsSpace && nextNeedsSpace) {
15722
- fn.parentNode?.insertBefore(doc.createTextNode(" "), next);
15757
+ }
15758
+ function fixIrregularTables(doc) {
15759
+ doc.querySelectorAll("table").forEach((table) => {
15760
+ const rows = table.querySelectorAll("tr");
15761
+ if (rows.length === 0) return;
15762
+ let maxColumns = 0;
15763
+ rows.forEach((row) => {
15764
+ let count = 0;
15765
+ row.querySelectorAll("td, th").forEach((cell) => {
15766
+ count += cell instanceof HTMLTableCellElement ? parseInt(cell.getAttribute("colspan") || "1", 10) : 1;
15767
+ });
15768
+ maxColumns = Math.max(maxColumns, count);
15769
+ });
15770
+ if (maxColumns > 1) {
15771
+ rows.forEach((row) => {
15772
+ const cells = row.querySelectorAll("td, th");
15773
+ if (cells.length === 1 && cells[0] instanceof HTMLTableCellElement) {
15774
+ const existing = parseInt(cells[0].getAttribute("colspan") || "1", 10);
15775
+ if (existing < maxColumns) {
15776
+ cells[0].setAttribute("colspan", maxColumns.toString());
15777
+ }
15778
+ }
15779
+ });
15723
15780
  }
15724
- fn.remove();
15725
15781
  });
15726
- return notes;
15782
+ }
15783
+ var DOMPURIFY_CONFIG = {
15784
+ ALLOWED_ATTR: ["class", "style", "id", "v", "usfm"],
15785
+ ALLOW_DATA_ATTR: true
15786
+ };
15787
+ function transformBibleHtml(html) {
15788
+ if (typeof window === "undefined" || !("DOMParser" in window)) {
15789
+ return { html, notes: {} };
15790
+ }
15791
+ const doc = new DOMParser().parseFromString(
15792
+ DOMPurify.sanitize(html, DOMPURIFY_CONFIG),
15793
+ "text/html"
15794
+ );
15795
+ wrapVerseContent(doc);
15796
+ const notes = extractNotesFromWrappedHtml(doc);
15797
+ addNbspToVerseLabels(doc);
15798
+ fixIrregularTables(doc);
15799
+ return { html: doc.body.innerHTML, notes };
15727
15800
  }
15728
15801
 
15729
15802
  // src/components/icons/footnote.tsx
@@ -15784,20 +15857,23 @@ var VerseFootnoteButton = memo(function VerseFootnoteButton2({
15784
15857
  dangerouslySetInnerHTML: { __html: verseNotes.verseHtml }
15785
15858
  }
15786
15859
  ),
15787
- /* @__PURE__ */ jsx20("ul", { className: "yv:list-none yv:p-0 yv:m-0 yv:space-y-1", children: verseNotes.notes.map((note, index) => /* @__PURE__ */ jsxs6(
15788
- "li",
15789
- {
15790
- className: "yv:flex yv:gap-2 yv:text-xs yv:border-b yv:border-border yv:py-2",
15791
- children: [
15792
- /* @__PURE__ */ jsxs6("span", { className: "", children: [
15793
- LETTERS[index] || index + 1,
15794
- "."
15795
- ] }),
15796
- /* @__PURE__ */ jsx20("span", { dangerouslySetInnerHTML: { __html: note } })
15797
- ]
15798
- },
15799
- LETTERS[index]
15800
- )) })
15860
+ /* @__PURE__ */ jsx20("ul", { className: "yv:list-none yv:p-0 yv:m-0 yv:space-y-1", children: verseNotes.notes.map((note, index) => {
15861
+ const marker = getFootnoteMarker(index);
15862
+ return /* @__PURE__ */ jsxs6(
15863
+ "li",
15864
+ {
15865
+ className: "yv:flex yv:gap-2 yv:text-xs yv:border-b yv:border-border yv:py-2",
15866
+ children: [
15867
+ /* @__PURE__ */ jsxs6("span", { className: "", children: [
15868
+ marker,
15869
+ "."
15870
+ ] }),
15871
+ /* @__PURE__ */ jsx20("span", { dangerouslySetInnerHTML: { __html: note } })
15872
+ ]
15873
+ },
15874
+ marker
15875
+ );
15876
+ }) })
15801
15877
  ] })
15802
15878
  }
15803
15879
  )
@@ -15814,57 +15890,39 @@ function BibleTextHtml({
15814
15890
  highlightedVerses = {}
15815
15891
  }) {
15816
15892
  const contentRef = useRef3(null);
15817
- const [placeholders, setPlaceholders] = useState3(/* @__PURE__ */ new Map());
15893
+ const [placeholders, setPlaceholders] = useState3([]);
15818
15894
  const providerTheme = useTheme3();
15819
15895
  const currentTheme = theme || providerTheme;
15820
15896
  useLayoutEffect(() => {
15821
15897
  if (!contentRef.current) return;
15822
15898
  contentRef.current.innerHTML = html;
15823
- const map2 = /* @__PURE__ */ new Map();
15824
- Object.keys(notes).forEach((verseNum) => {
15825
- const el = contentRef.current?.querySelector(`[data-verse-footnote="${verseNum}"]`);
15826
- if (el) map2.set(verseNum, el);
15899
+ const anchors = contentRef.current.querySelectorAll("[data-verse-footnote]");
15900
+ const result = [];
15901
+ anchors.forEach((el) => {
15902
+ const verseNum = el.getAttribute("data-verse-footnote");
15903
+ if (verseNum) result.push({ verseNum, el });
15827
15904
  });
15828
- setPlaceholders(map2);
15829
- }, [html, notes]);
15905
+ setPlaceholders(result);
15906
+ }, [html]);
15830
15907
  useLayoutEffect(() => {
15831
15908
  if (!contentRef.current) return;
15832
- const verseElements = contentRef.current.querySelectorAll(".yv-v[v]");
15833
- verseElements.forEach((el) => {
15909
+ contentRef.current.querySelectorAll(".yv-v[v]").forEach((el) => {
15834
15910
  const verseNum = parseInt(el.getAttribute("v") || "0", 10);
15835
- if (selectedVerses.includes(verseNum)) {
15836
- el.classList.add("yv-v-selected");
15837
- } else {
15838
- el.classList.remove("yv-v-selected");
15839
- }
15840
- if (highlightedVerses[verseNum]) {
15841
- el.classList.add("yv-v-highlighted");
15842
- } else {
15843
- el.classList.remove("yv-v-highlighted");
15844
- }
15911
+ el.classList.toggle("yv-v-selected", selectedVerses.includes(verseNum));
15912
+ el.classList.toggle("yv-v-highlighted", !!highlightedVerses[verseNum]);
15845
15913
  });
15846
15914
  }, [html, selectedVerses, highlightedVerses]);
15847
- const selectedVersesRef = useRef3(selectedVerses);
15848
- selectedVersesRef.current = selectedVerses;
15849
- useLayoutEffect(() => {
15850
- const element = contentRef.current;
15851
- if (!element || !onVerseSelect) return;
15852
- const handleClick = (e) => {
15853
- const target = e.target;
15854
- const verseEl = target.closest(".yv-v[v]");
15855
- if (!verseEl) return;
15856
- const verseNum = parseInt(verseEl.getAttribute("v") || "0", 10);
15857
- if (verseNum === 0) return;
15858
- const current = selectedVersesRef.current;
15859
- const newSelected = current.includes(verseNum) ? current.filter((v) => v !== verseNum) : [...current, verseNum].sort((a, b) => a - b);
15860
- onVerseSelect(newSelected);
15861
- };
15862
- element.addEventListener("click", handleClick);
15863
- return () => element.removeEventListener("click", handleClick);
15864
- }, [onVerseSelect]);
15915
+ const handleClick = onVerseSelect ? (e) => {
15916
+ const verseEl = e.target.closest(".yv-v[v]");
15917
+ if (!verseEl) return;
15918
+ const verseNum = parseInt(verseEl.getAttribute("v") || "0", 10);
15919
+ if (!verseNum) return;
15920
+ const newSelected = selectedVerses.includes(verseNum) ? selectedVerses.filter((v) => v !== verseNum) : [...selectedVerses, verseNum].sort((a, b) => a - b);
15921
+ onVerseSelect(newSelected);
15922
+ } : void 0;
15865
15923
  return /* @__PURE__ */ jsxs6(Fragment2, { children: [
15866
- /* @__PURE__ */ jsx20("div", { ref: contentRef }),
15867
- Array.from(placeholders.entries()).map(([verseNum, el]) => {
15924
+ /* @__PURE__ */ jsx20("div", { ref: contentRef, onClick: handleClick }),
15925
+ placeholders.map(({ verseNum, el }, index) => {
15868
15926
  const verseNotes = notes[verseNum];
15869
15927
  if (!verseNotes) return null;
15870
15928
  return createPortal(
@@ -15878,68 +15936,12 @@ function BibleTextHtml({
15878
15936
  theme: currentTheme
15879
15937
  }
15880
15938
  ),
15881
- el
15939
+ el,
15940
+ `${verseNum}-${index}`
15882
15941
  );
15883
15942
  })
15884
15943
  ] });
15885
15944
  }
15886
- var DOMPURIFY_CONFIG = {
15887
- ALLOWED_ATTR: ["class", "style", "id", "v", "usfm"],
15888
- ALLOW_DATA_ATTR: true
15889
- };
15890
- function yvDomTransformer(html, extractNotes = false) {
15891
- if (typeof window === "undefined" || !("DOMParser" in window)) {
15892
- return { html, notes: {} };
15893
- }
15894
- const doc = new DOMParser().parseFromString(
15895
- DOMPurify.sanitize(html, DOMPURIFY_CONFIG),
15896
- "text/html"
15897
- );
15898
- wrapVerseContent(doc);
15899
- const extractedNotes = extractNotes ? extractNotesFromWrappedHtml(doc) : {};
15900
- const verseLabels = doc.querySelectorAll(".yv-vlbl");
15901
- verseLabels.forEach((label) => {
15902
- const text = label.textContent || "";
15903
- if (!text.endsWith(NON_BREAKING_SPACE)) {
15904
- label.textContent = text + NON_BREAKING_SPACE;
15905
- }
15906
- });
15907
- const tables = doc.querySelectorAll("table");
15908
- tables.forEach((table) => {
15909
- const rows = table.querySelectorAll("tr");
15910
- if (rows.length === 0) return;
15911
- let maxColumns = 0;
15912
- rows.forEach((row) => {
15913
- const cells = row.querySelectorAll("td, th");
15914
- let rowColumnCount = 0;
15915
- cells.forEach((cell) => {
15916
- if (cell instanceof HTMLTableCellElement) {
15917
- const colspan = parseInt(cell.getAttribute("colspan") || "1", 10);
15918
- rowColumnCount += colspan;
15919
- } else {
15920
- rowColumnCount += 1;
15921
- }
15922
- });
15923
- maxColumns = Math.max(maxColumns, rowColumnCount);
15924
- });
15925
- if (maxColumns > 1) {
15926
- rows.forEach((row) => {
15927
- const cells = row.querySelectorAll("td, th");
15928
- if (cells.length === 1) {
15929
- const cell = cells[0];
15930
- if (cell instanceof HTMLTableCellElement) {
15931
- const existingColspan = parseInt(cell.getAttribute("colspan") || "1", 10);
15932
- if (existingColspan < maxColumns) {
15933
- cell.setAttribute("colspan", maxColumns.toString());
15934
- }
15935
- }
15936
- }
15937
- });
15938
- }
15939
- });
15940
- const modifiedHtml = doc.body.innerHTML;
15941
- return { html: modifiedHtml, notes: extractedNotes };
15942
- }
15943
15945
  var Verse = {
15944
15946
  /**
15945
15947
  * Renders a single verse with superscript number and text.
@@ -15980,12 +15982,9 @@ var Verse = {
15980
15982
  onVerseSelect,
15981
15983
  highlightedVerses
15982
15984
  }, ref) => {
15983
- const [transformedData, setTransformedData] = useState3({ html, notes: {} });
15985
+ const transformedData = useMemo3(() => transformBibleHtml(html), [html]);
15984
15986
  const providerTheme = useTheme3();
15985
15987
  const currentTheme = theme || providerTheme;
15986
- useEffect3(() => {
15987
- setTransformedData(yvDomTransformer(html, true));
15988
- }, [html]);
15989
15988
  return /* @__PURE__ */ jsx20(
15990
15989
  "section",
15991
15990
  {
@@ -16145,10 +16144,10 @@ function Root6({
16145
16144
  setCurrentFontFamily(savedFontFamily);
16146
16145
  }
16147
16146
  }, []);
16148
- useEffect4(() => {
16147
+ useEffect3(() => {
16149
16148
  localStorage.setItem("youversion-platform:reader:font-size", currentFontSize.toString());
16150
16149
  }, [currentFontSize]);
16151
- useEffect4(() => {
16150
+ useEffect3(() => {
16152
16151
  localStorage.setItem("youversion-platform:reader:font-family", currentFontFamily);
16153
16152
  }, [currentFontFamily]);
16154
16153
  const providerTheme = useTheme4();
@@ -16191,7 +16190,7 @@ function Content5() {
16191
16190
  } = useBibleReaderContext();
16192
16191
  const { books } = useBooks2(versionId);
16193
16192
  const { version: version2 } = useVersion2(versionId);
16194
- const bookData = useMemo3(() => {
16193
+ const bookData = useMemo4(() => {
16195
16194
  return books?.data?.find((b) => b.id === book);
16196
16195
  }, [books?.data, book]);
16197
16196
  const usfmReference = `${book}.${chapter}`;
@@ -16249,7 +16248,7 @@ function Content5() {
16249
16248
  function UserMenu() {
16250
16249
  const { auth, signIn, signOut, userInfo } = useYVAuth();
16251
16250
  return /* @__PURE__ */ jsxs7(Popover, { children: [
16252
- /* @__PURE__ */ jsx21(PopoverTrigger, { "data-testid": "user-menu-trigger", children: /* @__PURE__ */ jsx21(Button, { size: "icon", variant: "secondary", children: auth.isAuthenticated && userInfo?.avatarUrlFormat ? /* @__PURE__ */ jsx21(
16251
+ /* @__PURE__ */ jsx21(PopoverTrigger, { asChild: true, "data-testid": "user-menu-trigger", children: /* @__PURE__ */ jsx21(Button, { size: "icon", variant: "secondary", children: auth.isAuthenticated && userInfo?.avatarUrlFormat ? /* @__PURE__ */ jsx21(
16253
16252
  "img",
16254
16253
  {
16255
16254
  src: userInfo.getAvatarUrl(32, 32)?.toString(),
@@ -16342,7 +16341,7 @@ function Toolbar({ border = "top" }) {
16342
16341
  )
16343
16342
  ] }),
16344
16343
  /* @__PURE__ */ jsxs7(Popover, { children: [
16345
- /* @__PURE__ */ jsx21(PopoverTrigger, { "aria-label": "Settings", children: /* @__PURE__ */ jsx21(Button, { size: "icon", variant: "secondary", children: /* @__PURE__ */ jsx21(GearIcon, { className: "yv:text-foreground" }) }) }),
16344
+ /* @__PURE__ */ jsx21(PopoverTrigger, { asChild: true, "aria-label": "Settings", children: /* @__PURE__ */ jsx21(Button, { size: "icon", variant: "secondary", children: /* @__PURE__ */ jsx21(GearIcon, { className: "yv:text-foreground" }) }) }),
16346
16345
  /* @__PURE__ */ jsx21(PopoverContent, { sideOffset: 16, heading: "Reader Settings", theme: background, children: /* @__PURE__ */ jsxs7("div", { className: "yv:flex yv:flex-col yv:gap-4 yv:p-4", children: [
16347
16346
  /* @__PURE__ */ jsxs7("div", { className: "yv:grid yv:grid-cols-2", children: [
16348
16347
  /* @__PURE__ */ jsx21(
@@ -16433,7 +16432,7 @@ function Toolbar({ border = "top" }) {
16433
16432
  var BibleReader = Object.assign({}, { Root: Root6, Content: Content5, Toolbar });
16434
16433
 
16435
16434
  // src/components/YouVersionAuthButton.tsx
16436
- import React10, { useMemo as useMemo4 } from "react";
16435
+ import React10, { useMemo as useMemo5 } from "react";
16437
16436
 
16438
16437
  // src/components/icons/loader.tsx
16439
16438
  import { jsx as jsx22 } from "react/jsx-runtime";
@@ -16595,7 +16594,7 @@ var YouVersionAuthButton = React10.forwardRef(
16595
16594
  }
16596
16595
  };
16597
16596
  const buttonLoading = auth.isLoading;
16598
- const buttonText = useMemo4(() => {
16597
+ const buttonText = useMemo5(() => {
16599
16598
  if (text) return text;
16600
16599
  const isSignOut = mode === "signOut" || mode === "auto" && auth.isAuthenticated;
16601
16600
  if (size === "short") {
@@ -16967,11 +16966,11 @@ function VerseOfTheDay({
16967
16966
  );
16968
16967
  }
16969
16968
 
16970
- // src/components/bible-widget-view.tsx
16969
+ // src/components/bible-card.tsx
16971
16970
  import { usePassage as usePassage3, useVersion as useVersion4, useTheme as useTheme7 } from "@youversion/platform-react-hooks";
16972
16971
  import { useState as useState5 } from "react";
16973
16972
  import { jsx as jsx29, jsxs as jsxs14 } from "react/jsx-runtime";
16974
- function BibleWidgetView({
16973
+ function BibleCard({
16975
16974
  reference,
16976
16975
  versionId,
16977
16976
  background,
@@ -17033,7 +17032,7 @@ function BibleWidgetView({
17033
17032
  }
17034
17033
  ),
17035
17034
  /* @__PURE__ */ jsxs14("div", { className: "yv:grid yv:grid-cols-[1fr_auto] yv:gap-4 yv:items-center yv:mt-4", children: [
17036
- /* @__PURE__ */ jsx29("div", { className: "yv:text-balance yv:text-muted-foreground yv:justify-self-start yv:font-bold yv:text-[0.5rem]", children: version2?.copyright }),
17035
+ /* @__PURE__ */ jsx29("p", { className: "yv:text-balance yv:text-muted-foreground yv:justify-self-start yv:font-bold yv:text-[0.5rem]", children: version2?.copyright }),
17037
17036
  /* @__PURE__ */ jsx29("div", { className: "yv:justify-self-end", children: /* @__PURE__ */ jsx29(SvgComponent, { fontSize: 12 }) })
17038
17037
  ] })
17039
17038
  ]
@@ -17041,6 +17040,9 @@ function BibleWidgetView({
17041
17040
  );
17042
17041
  }
17043
17042
 
17043
+ // src/components/bible-widget-view.tsx
17044
+ var BibleWidgetView = BibleCard;
17045
+
17044
17046
  // src/index.ts
17045
17047
  import {
17046
17048
  YouVersionProvider,
@@ -17051,6 +17053,7 @@ export {
17051
17053
  ApiClient,
17052
17054
  BOOK_CANON,
17053
17055
  BOOK_IDS,
17056
+ BibleCard,
17054
17057
  BibleChapterPicker,
17055
17058
  BibleClient,
17056
17059
  BibleReader,
@@ -1,5 +1,13 @@
1
- export declare const NON_BREAKING_SPACE = "\u00A0";
2
- export declare const LETTERS = "abcdefghijklmnopqrstuvwxyz";
1
+ /**
2
+ * Converts a 0-based footnote index into an alphabetic marker.
3
+ *
4
+ * Examples with LETTERS = "abcdefghijklmnopqrstuvwxyz":
5
+ * 0 -> "a", 25 -> "z", 26 -> "aa", 27 -> "ab"
6
+ *
7
+ * This uses spreadsheet-style indexing and derives its base from
8
+ * LETTERS.length so there are no hardcoded numeric assumptions.
9
+ */
10
+ export declare function getFootnoteMarker(index: number): string;
3
11
  export type VerseNotes = {
4
12
  verseHtml: string;
5
13
  notes: string[];
@@ -8,24 +16,16 @@ export declare const INTER_FONT: "\"Inter\", sans-serif";
8
16
  export declare const SOURCE_SERIF_FONT: "\"Source Serif 4\", serif";
9
17
  export type FontFamily = typeof INTER_FONT | typeof SOURCE_SERIF_FONT | (string & {});
10
18
  /**
11
- * Wraps verse content in `yv-v` elements for easier CSS targeting.
12
- *
13
- * Transforms empty verse markers into wrapping containers. When a verse spans
14
- * multiple paragraphs, creates duplicate wrappers in each paragraph (Bible.com pattern).
15
- *
16
- * Before: <span class="yv-v" v="1"></span><span class="yv-vlbl">1</span>Text...
17
- * After: <span class="yv-v" v="1"><span class="yv-vlbl">1</span>Text...</span>
19
+ * Full transformation pipeline for Bible HTML from the API.
18
20
  *
19
- * This enables simple CSS selectors like `.yv-v[v="1"] { background: yellow; }`
21
+ * 1. Sanitize (DOMPurify)
22
+ * 2. Wrap verse content in selectable spans
23
+ * 3. Extract footnotes and replace with portal anchors
24
+ * 4. Add non-breaking spaces to verse labels
25
+ * 5. Fix irregular table layouts
20
26
  */
21
- export declare function wrapVerseContent(doc: Document): void;
22
- /**
23
- * Extracts footnotes from wrapped verse HTML and prepares data for footnote popovers.
24
- *
25
- * This function assumes verses are already wrapped in `.yv-v[v]` elements (by wrapVerseContent).
26
- * It uses `.closest('.yv-v[v]')` to find which verse each footnote belongs to.
27
- *
28
- * @returns Notes data for popovers, keyed by verse number
29
- */
30
- export declare function extractNotesFromWrappedHtml(doc: Document): Record<string, VerseNotes>;
27
+ export declare function transformBibleHtml(html: string): {
28
+ html: string;
29
+ notes: Record<string, VerseNotes>;
30
+ };
31
31
  //# sourceMappingURL=verse-html-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"verse-html-utils.d.ts","sourceRoot":"","sources":["../../src/lib/verse-html-utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAE3C,eAAO,MAAM,OAAO,+BAA+B,CAAC;AAEpD,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAG,uBAA8B,CAAC;AACzD,eAAO,MAAM,iBAAiB,EAAG,2BAAkC,CAAC;AACpE,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,GAAG,OAAO,iBAAiB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAEtF;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CA0IpD;AAGD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAkGrF"}
1
+ {"version":3,"file":"verse-html-utils.d.ts","sourceRoot":"","sources":["../../src/lib/verse-html-utils.ts"],"names":[],"mappings":"AAMA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAavD;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAG,uBAA8B,CAAC;AACzD,eAAO,MAAM,iBAAiB,EAAG,2BAAkC,CAAC;AACpE,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,GAAG,OAAO,iBAAiB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AA0UtF;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CAAE,CAgBpG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youversion/platform-react-ui",
3
- "version": "1.14.3",
3
+ "version": "1.15.0",
4
4
  "description": "React SDK for YouVersion Platform",
5
5
  "license": "TBD",
6
6
  "type": "module",
@@ -38,8 +38,8 @@
38
38
  "isomorphic-dompurify": "2.23.0",
39
39
  "tailwind-merge": "3.3.1",
40
40
  "tw-animate-css": "1.4.0",
41
- "@youversion/platform-core": "1.14.3",
42
- "@youversion/platform-react-hooks": "1.14.3"
41
+ "@youversion/platform-core": "1.15.0",
42
+ "@youversion/platform-react-hooks": "1.15.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "react": ">=19.1.0 <20.0.0",