@universityofmaryland/web-feeds-library 1.3.7 → 1.3.9

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.
Files changed (35) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +1 -1
  3. package/dist/factory/helpers/displayHandler.d.ts.map +1 -1
  4. package/dist/factory/helpers/displayHandler.js +2 -1
  5. package/dist/factory/helpers/displayHandler.js.map +1 -1
  6. package/dist/feeds/events/_types.d.ts +1 -1
  7. package/dist/feeds/events/_types.d.ts.map +1 -1
  8. package/dist/feeds/events/grid.d.ts.map +1 -1
  9. package/dist/feeds/events/grid.js +2 -11
  10. package/dist/feeds/events/grid.js.map +1 -1
  11. package/dist/feeds/events/grouped.d.ts.map +1 -1
  12. package/dist/feeds/events/grouped.js +4 -14
  13. package/dist/feeds/events/grouped.js.map +1 -1
  14. package/dist/feeds/events/image.d.ts +8 -0
  15. package/dist/feeds/events/image.d.ts.map +1 -0
  16. package/dist/feeds/events/image.js +17 -0
  17. package/dist/feeds/events/image.js.map +1 -0
  18. package/dist/feeds/events/list.d.ts.map +1 -1
  19. package/dist/feeds/events/list.js +2 -13
  20. package/dist/feeds/events/list.js.map +1 -1
  21. package/dist/feeds/experts/in-the-news.d.ts.map +1 -1
  22. package/dist/feeds/experts/in-the-news.js +2 -1
  23. package/dist/feeds/experts/in-the-news.js.map +1 -1
  24. package/dist/feeds/news/featured.d.ts.map +1 -1
  25. package/dist/feeds/news/featured.js +7 -6
  26. package/dist/feeds/news/featured.js.map +1 -1
  27. package/dist/states/pagination.d.ts.map +1 -1
  28. package/dist/states/pagination.js +3 -2
  29. package/dist/states/pagination.js.map +1 -1
  30. package/dist/strategies/fetch/events.d.ts.map +1 -1
  31. package/dist/strategies/fetch/events.js +5 -1
  32. package/dist/strategies/fetch/events.js.map +1 -1
  33. package/dist/types/core.d.ts +1 -0
  34. package/dist/types/core.d.ts.map +1 -1
  35. package/package.json +21 -17
package/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) 2023 University of Maryland.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # University of Maryland Feeds Library
2
2
 
3
- [![Feeds Version](https://img.shields.io/badge/Feeds-v1.3.7-blue)](https://www.npmjs.com/package/@universityofmaryland/web-feeds-library)
3
+ [![Feeds Version](https://img.shields.io/badge/Feeds-v1.3.9-blue)](https://www.npmjs.com/package/@universityofmaryland/web-feeds-library)
4
4
 
5
5
  Dynamic content feed components for displaying University of Maryland news, events, and academic information with automatic updates, caching, and brand-compliant styling.
6
6
 
@@ -1 +1 @@
1
- {"version":3,"file":"displayHandler.d.ts","sourceRoot":"","sources":["../../../source/factory/helpers/displayHandler.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,eAAe,EACf,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,UAAU,oBAAoB,CAAC,KAAK;IAElC,eAAe,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IAExC,cAAc,EAAE,cAAc,CAAC;IAE/B,OAAO,EAAE,WAAW,CAAC;IAErB,kBAAkB,EAAE,kBAAkB,CAAC;IAEvC,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B,mBAAmB,EAAE,MAAM,CAAC;IAE5B,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAKD,wBAAsB,eAAe,CAAC,EACpC,UAAU,EACV,MAAM,GACP,EAAE;IACD,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB;AAsED,wBAAgB,qBAAqB,CAAC,KAAK,EACzC,MAAM,EAAE,oBAAoB,CAAC,KAAK,CAAC;8BAiBF,GAAG;4BAwC3B,mBAAmB,CAAC,KAAK,CAAC,KAChC,OAAO,CAAC,IAAI,CAAC;gCAyDyB;QACvC,QAAQ,EAAE,KAAK,EAAE,CAAC;QAClB,aAAa,EAAE,YAAY,CAAC;QAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;KAC1B,KAAG,OAAO,CAAC,IAAI,CAAC;EAuDlB"}
1
+ {"version":3,"file":"displayHandler.d.ts","sourceRoot":"","sources":["../../../source/factory/helpers/displayHandler.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,eAAe,EACf,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,UAAU,oBAAoB,CAAC,KAAK;IAElC,eAAe,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IAExC,cAAc,EAAE,cAAc,CAAC;IAE/B,OAAO,EAAE,WAAW,CAAC;IAErB,kBAAkB,EAAE,kBAAkB,CAAC;IAEvC,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B,mBAAmB,EAAE,MAAM,CAAC;IAE5B,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAKD,wBAAsB,eAAe,CAAC,EACpC,UAAU,EACV,MAAM,GACP,EAAE;IACD,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB;AAsED,wBAAgB,qBAAqB,CAAC,KAAK,EACzC,MAAM,EAAE,oBAAoB,CAAC,KAAK,CAAC;8BAiBF,GAAG;4BAwC3B,mBAAmB,CAAC,KAAK,CAAC,KAChC,OAAO,CAAC,IAAI,CAAC;gCA0DyB;QACvC,QAAQ,EAAE,KAAK,EAAE,CAAC;QAClB,aAAa,EAAE,YAAY,CAAC;QAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;KAC1B,KAAG,OAAO,CAAC,IAAI,CAAC;EAuDlB"}
@@ -106,7 +106,8 @@ function createDisplayHandlers(config) {
106
106
  totalEntries: helpers.getTotalEntries(),
107
107
  offset: helpers.getOffset(),
108
108
  isLazyLoad: true,
109
- callback: lazyLoadCallback
109
+ callback: lazyLoadCallback,
110
+ isThemeDark: cardMappingOptions.isThemeDark
110
111
  });
111
112
  const paginationElement = pagination.render(container);
112
113
  if (paginationElement) {
@@ -1 +1 @@
1
- {"version":3,"file":"displayHandler.js","sources":["../../../source/factory/helpers/displayHandler.ts"],"sourcesContent":["/**\n * Display Handler Utilities\n *\n * Creates handlers for displaying feed results using a display strategy.\n * Handles both initial display and lazy-loaded results.\n *\n * @module factory/helpers/displayHandler\n */\n\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport * as Styles from '@universityofmaryland/web-styles-library';\nimport * as typography from '@universityofmaryland/web-styles-library/typography';\nimport { theme } from '@universityofmaryland/web-utilities-library/theme';\nimport { PaginationState, EmptyState, Announcer } from '../../states';\nimport { events } from '../../helpers';\nimport {\n DisplayStrategy,\n FeedHelpers,\n LayoutStrategy,\n CardMappingOptions,\n DisplayResultsProps,\n NoResultsConfig,\n} from '../core/types';\nimport { ElementModel } from '../../_types';\n\n/**\n * Configuration for creating display handlers\n */\ninterface DisplayHandlerConfig<TData> {\n /** Strategy for displaying entries */\n displayStrategy: DisplayStrategy<TData>;\n /** Strategy for creating layout */\n layoutStrategy: LayoutStrategy;\n /** Feed helper functions */\n helpers: FeedHelpers;\n /** Options for card mapping */\n cardMappingOptions: CardMappingOptions;\n /** Whether lazy loading is enabled */\n isLazyLoad?: boolean;\n /** Number of columns to show */\n numberOfColumnsToShow?: number;\n /** Number of rows to start with */\n numberOfRowsToStart: number;\n /** Configuration for no results state */\n noResultsConfig?: NoResultsConfig;\n /** Callback for lazy load */\n lazyLoadCallback?: () => Promise<void>;\n}\n\n/**\n * Set shadow root styles with CSS optimization\n */\nexport async function setShadowStyles({\n shadowRoot,\n styles,\n}: {\n shadowRoot: ShadowRoot;\n styles: string;\n}): Promise<void> {\n const styleElement = document.createElement('style');\n const optimizedCss = await Styles.utilities.transform.css.removeDuplicates(\n styles,\n );\n styleElement.textContent = optimizedCss;\n shadowRoot.appendChild(styleElement);\n}\n\n/**\n * Create fallback message for category-filtered feeds with no results\n */\nfunction createFallbackMessage(\n categoryNames: string[],\n isThemeDark: boolean = false,\n): ElementModel {\n const formattedNames = categoryNames.join(', ');\n const message = `No events found for \"${formattedNames}\" <span> Other upcoming events:</span>`;\n\n const messageElement = new ElementBuilder(document.createElement('p'))\n .styled(\n typography.sans.compose('extralarge', {\n theme: theme.fontColor(isThemeDark),\n }),\n )\n .withStyles({\n textAlign: 'center',\n margin: '0 auto',\n\n ['& span']: {\n display: 'block',\n },\n })\n .withHTML(message);\n\n const container = new ElementBuilder(document.createElement('div'))\n .withClassName('feed-events-fallback-message')\n .withChild(messageElement)\n .withStyles({\n element: {\n marginBottom: Styles.token.spacing.lg,\n padding: `${Styles.token.spacing.md} ${Styles.token.spacing.lg}`,\n },\n })\n .build();\n\n return container;\n}\n\n/**\n * Create display handlers for a feed\n *\n * Returns handlers for:\n * - Displaying initial results\n * - Displaying lazy-loaded results\n * - Displaying no results state\n *\n * @param config - Configuration for the display handlers\n * @returns Display handler functions\n *\n * @example\n * ```typescript\n * const handlers = createDisplayHandlers({\n * displayStrategy: eventsDisplayStrategy,\n * layoutStrategy: gridGapLayout,\n * helpers: feedHelpers,\n * cardMappingOptions: { isThemeDark: false },\n * isLazyLoad: true,\n * numberOfColumnsToShow: 3,\n * numberOfRowsToStart: 2,\n * });\n *\n * // Use handlers\n * handlers.displayResultStart({ feedData, layoutElement });\n * await handlers.displayResults({ feedData });\n * ```\n */\nexport function createDisplayHandlers<TData>(\n config: DisplayHandlerConfig<TData>,\n) {\n const {\n displayStrategy,\n layoutStrategy,\n helpers,\n cardMappingOptions,\n isLazyLoad,\n numberOfColumnsToShow = 1,\n numberOfRowsToStart,\n noResultsConfig = {},\n lazyLoadCallback,\n } = config;\n\n /**\n * Handle displaying no results\n */\n const displayNoResults = (props: any) => {\n const container = helpers.getContainer();\n const shadowRoot = helpers.getShadowRoot();\n const { message, linkUrl, linkText, isThemeDark } = {\n ...noResultsConfig,\n ...props,\n };\n\n const emptyState = new EmptyState({\n message,\n linkUrl,\n linkText,\n isThemeDark,\n });\n\n const announcer = new Announcer({ message: message || 'No results found' });\n\n container.innerHTML = '';\n emptyState.render(container);\n container.appendChild(announcer.getElement());\n helpers.setStyles(emptyState.styles);\n\n events.dispatch(container, events.eventNames.FEED_ERROR, {\n error: 'No results found',\n message,\n isThemeDark,\n });\n\n setTimeout(() => {\n const styles = helpers.getStyles();\n if (shadowRoot) {\n setShadowStyles({ shadowRoot, styles });\n }\n }, 100);\n };\n\n /**\n * Handle displaying lazy-loaded results\n */\n const displayResults = async (\n props: DisplayResultsProps<TData>,\n ): Promise<void> => {\n const { feedData } = props;\n const container = helpers.getContainer();\n const grid = container.querySelector(\n `#${layoutStrategy.getId()}`,\n ) as HTMLDivElement;\n\n // Remove existing loading and pagination states\n const existingLoader = container.querySelector('.umd-loader-container');\n existingLoader?.remove();\n\n const existingPagination = container.querySelector(\n `.${Styles.layout.alignment.block.center.className}`,\n );\n existingPagination?.remove();\n\n helpers.setOffset(feedData.length);\n\n // Map entries to cards using display strategy\n const entries = feedData.map((entry) =>\n displayStrategy.mapEntryToCard(entry, cardMappingOptions),\n );\n\n // Append entries to grid\n entries.forEach((entry) => {\n grid.appendChild(entry.element);\n helpers.setStyles(entry.styles);\n });\n\n // Add pagination if lazy load is enabled\n if (isLazyLoad && lazyLoadCallback) {\n const pagination = new PaginationState({\n totalEntries: helpers.getTotalEntries(),\n offset: helpers.getOffset(),\n isLazyLoad: true,\n callback: lazyLoadCallback,\n });\n\n const paginationElement = pagination.render(container);\n if (paginationElement) {\n helpers.setStyles(paginationElement.styles);\n }\n }\n\n // Update shadow root styles if needed\n const shadowRoot = helpers.getShadowRoot();\n if (shadowRoot) {\n await setShadowStyles({\n shadowRoot,\n styles: helpers.getStyles(),\n });\n }\n };\n\n /**\n * Handle displaying initial results\n */\n const displayResultStart = async (props: {\n feedData: TData[];\n layoutElement: ElementModel;\n isFallback?: boolean;\n categoryNames?: string[];\n }): Promise<void> => {\n const { feedData, layoutElement, isFallback, categoryNames } = props;\n const container = helpers.getContainer();\n const totalEntries = helpers.getTotalEntries();\n const showAmount = numberOfColumnsToShow * numberOfRowsToStart;\n const message = isLazyLoad\n ? `Showing ${showAmount} of ${totalEntries} articles`\n : `Showing ${showAmount} articles`;\n\n // If this is a fallback, add a message about the category FIRST\n if (isFallback && categoryNames && categoryNames.length > 0) {\n const fallbackMessage = createFallbackMessage(\n categoryNames,\n cardMappingOptions.isThemeDark || false,\n );\n container.appendChild(fallbackMessage.element);\n helpers.setStyles(fallbackMessage.styles);\n\n // Inject fallback message styles into shadow DOM immediately\n const shadowRoot = helpers.getShadowRoot();\n if (shadowRoot) {\n await setShadowStyles({\n shadowRoot,\n styles: helpers.getStyles(),\n });\n }\n }\n\n // Set layout ID and append to container\n layoutElement.element.setAttribute('id', layoutStrategy.getId());\n container.appendChild(layoutElement.element);\n helpers.setStyles(layoutElement.styles);\n\n // Dispatch feed loaded event\n events.dispatch(container, events.eventNames.FEED_LOADED, {\n items: feedData,\n count: feedData.length,\n total: totalEntries || feedData.length,\n isFallback,\n categoryNames,\n });\n\n // Display the results\n await displayResults({ feedData });\n\n // Add announcer\n const announcer = new Announcer({ message });\n container.appendChild(announcer.getElement());\n };\n\n return {\n displayNoResults,\n displayResults,\n displayResultStart,\n };\n}\n"],"names":["events.dispatch","events.eventNames"],"mappings":"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"}
1
+ {"version":3,"file":"displayHandler.js","sources":["../../../source/factory/helpers/displayHandler.ts"],"sourcesContent":["/**\n * Display Handler Utilities\n *\n * Creates handlers for displaying feed results using a display strategy.\n * Handles both initial display and lazy-loaded results.\n *\n * @module factory/helpers/displayHandler\n */\n\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport * as Styles from '@universityofmaryland/web-styles-library';\nimport * as typography from '@universityofmaryland/web-styles-library/typography';\nimport { theme } from '@universityofmaryland/web-utilities-library/theme';\nimport { PaginationState, EmptyState, Announcer } from '../../states';\nimport { events } from '../../helpers';\nimport {\n DisplayStrategy,\n FeedHelpers,\n LayoutStrategy,\n CardMappingOptions,\n DisplayResultsProps,\n NoResultsConfig,\n} from '../core/types';\nimport { ElementModel } from '../../_types';\n\n/**\n * Configuration for creating display handlers\n */\ninterface DisplayHandlerConfig<TData> {\n /** Strategy for displaying entries */\n displayStrategy: DisplayStrategy<TData>;\n /** Strategy for creating layout */\n layoutStrategy: LayoutStrategy;\n /** Feed helper functions */\n helpers: FeedHelpers;\n /** Options for card mapping */\n cardMappingOptions: CardMappingOptions;\n /** Whether lazy loading is enabled */\n isLazyLoad?: boolean;\n /** Number of columns to show */\n numberOfColumnsToShow?: number;\n /** Number of rows to start with */\n numberOfRowsToStart: number;\n /** Configuration for no results state */\n noResultsConfig?: NoResultsConfig;\n /** Callback for lazy load */\n lazyLoadCallback?: () => Promise<void>;\n}\n\n/**\n * Set shadow root styles with CSS optimization\n */\nexport async function setShadowStyles({\n shadowRoot,\n styles,\n}: {\n shadowRoot: ShadowRoot;\n styles: string;\n}): Promise<void> {\n const styleElement = document.createElement('style');\n const optimizedCss = await Styles.utilities.transform.css.removeDuplicates(\n styles,\n );\n styleElement.textContent = optimizedCss;\n shadowRoot.appendChild(styleElement);\n}\n\n/**\n * Create fallback message for category-filtered feeds with no results\n */\nfunction createFallbackMessage(\n categoryNames: string[],\n isThemeDark: boolean = false,\n): ElementModel {\n const formattedNames = categoryNames.join(', ');\n const message = `No events found for \"${formattedNames}\" <span> Other upcoming events:</span>`;\n\n const messageElement = new ElementBuilder(document.createElement('p'))\n .styled(\n typography.sans.compose('extralarge', {\n theme: theme.fontColor(isThemeDark),\n }),\n )\n .withStyles({\n textAlign: 'center',\n margin: '0 auto',\n\n ['& span']: {\n display: 'block',\n },\n })\n .withHTML(message);\n\n const container = new ElementBuilder(document.createElement('div'))\n .withClassName('feed-events-fallback-message')\n .withChild(messageElement)\n .withStyles({\n element: {\n marginBottom: Styles.token.spacing.lg,\n padding: `${Styles.token.spacing.md} ${Styles.token.spacing.lg}`,\n },\n })\n .build();\n\n return container;\n}\n\n/**\n * Create display handlers for a feed\n *\n * Returns handlers for:\n * - Displaying initial results\n * - Displaying lazy-loaded results\n * - Displaying no results state\n *\n * @param config - Configuration for the display handlers\n * @returns Display handler functions\n *\n * @example\n * ```typescript\n * const handlers = createDisplayHandlers({\n * displayStrategy: eventsDisplayStrategy,\n * layoutStrategy: gridGapLayout,\n * helpers: feedHelpers,\n * cardMappingOptions: { isThemeDark: false },\n * isLazyLoad: true,\n * numberOfColumnsToShow: 3,\n * numberOfRowsToStart: 2,\n * });\n *\n * // Use handlers\n * handlers.displayResultStart({ feedData, layoutElement });\n * await handlers.displayResults({ feedData });\n * ```\n */\nexport function createDisplayHandlers<TData>(\n config: DisplayHandlerConfig<TData>,\n) {\n const {\n displayStrategy,\n layoutStrategy,\n helpers,\n cardMappingOptions,\n isLazyLoad,\n numberOfColumnsToShow = 1,\n numberOfRowsToStart,\n noResultsConfig = {},\n lazyLoadCallback,\n } = config;\n\n /**\n * Handle displaying no results\n */\n const displayNoResults = (props: any) => {\n const container = helpers.getContainer();\n const shadowRoot = helpers.getShadowRoot();\n const { message, linkUrl, linkText, isThemeDark } = {\n ...noResultsConfig,\n ...props,\n };\n\n const emptyState = new EmptyState({\n message,\n linkUrl,\n linkText,\n isThemeDark,\n });\n\n const announcer = new Announcer({ message: message || 'No results found' });\n\n container.innerHTML = '';\n emptyState.render(container);\n container.appendChild(announcer.getElement());\n helpers.setStyles(emptyState.styles);\n\n events.dispatch(container, events.eventNames.FEED_ERROR, {\n error: 'No results found',\n message,\n isThemeDark,\n });\n\n setTimeout(() => {\n const styles = helpers.getStyles();\n if (shadowRoot) {\n setShadowStyles({ shadowRoot, styles });\n }\n }, 100);\n };\n\n /**\n * Handle displaying lazy-loaded results\n */\n const displayResults = async (\n props: DisplayResultsProps<TData>,\n ): Promise<void> => {\n const { feedData } = props;\n const container = helpers.getContainer();\n const grid = container.querySelector(\n `#${layoutStrategy.getId()}`,\n ) as HTMLDivElement;\n\n // Remove existing loading and pagination states\n const existingLoader = container.querySelector('.umd-loader-container');\n existingLoader?.remove();\n\n const existingPagination = container.querySelector(\n `.${Styles.layout.alignment.block.center.className}`,\n );\n existingPagination?.remove();\n\n helpers.setOffset(feedData.length);\n\n // Map entries to cards using display strategy\n const entries = feedData.map((entry) =>\n displayStrategy.mapEntryToCard(entry, cardMappingOptions),\n );\n\n // Append entries to grid\n entries.forEach((entry) => {\n grid.appendChild(entry.element);\n helpers.setStyles(entry.styles);\n });\n\n // Add pagination if lazy load is enabled\n if (isLazyLoad && lazyLoadCallback) {\n const pagination = new PaginationState({\n totalEntries: helpers.getTotalEntries(),\n offset: helpers.getOffset(),\n isLazyLoad: true,\n callback: lazyLoadCallback,\n isThemeDark: cardMappingOptions.isThemeDark,\n });\n\n const paginationElement = pagination.render(container);\n if (paginationElement) {\n helpers.setStyles(paginationElement.styles);\n }\n }\n\n // Update shadow root styles if needed\n const shadowRoot = helpers.getShadowRoot();\n if (shadowRoot) {\n await setShadowStyles({\n shadowRoot,\n styles: helpers.getStyles(),\n });\n }\n };\n\n /**\n * Handle displaying initial results\n */\n const displayResultStart = async (props: {\n feedData: TData[];\n layoutElement: ElementModel;\n isFallback?: boolean;\n categoryNames?: string[];\n }): Promise<void> => {\n const { feedData, layoutElement, isFallback, categoryNames } = props;\n const container = helpers.getContainer();\n const totalEntries = helpers.getTotalEntries();\n const showAmount = numberOfColumnsToShow * numberOfRowsToStart;\n const message = isLazyLoad\n ? `Showing ${showAmount} of ${totalEntries} articles`\n : `Showing ${showAmount} articles`;\n\n // If this is a fallback, add a message about the category FIRST\n if (isFallback && categoryNames && categoryNames.length > 0) {\n const fallbackMessage = createFallbackMessage(\n categoryNames,\n cardMappingOptions.isThemeDark || false,\n );\n container.appendChild(fallbackMessage.element);\n helpers.setStyles(fallbackMessage.styles);\n\n // Inject fallback message styles into shadow DOM immediately\n const shadowRoot = helpers.getShadowRoot();\n if (shadowRoot) {\n await setShadowStyles({\n shadowRoot,\n styles: helpers.getStyles(),\n });\n }\n }\n\n // Set layout ID and append to container\n layoutElement.element.setAttribute('id', layoutStrategy.getId());\n container.appendChild(layoutElement.element);\n helpers.setStyles(layoutElement.styles);\n\n // Dispatch feed loaded event\n events.dispatch(container, events.eventNames.FEED_LOADED, {\n items: feedData,\n count: feedData.length,\n total: totalEntries || feedData.length,\n isFallback,\n categoryNames,\n });\n\n // Display the results\n await displayResults({ feedData });\n\n // Add announcer\n const announcer = new Announcer({ message });\n container.appendChild(announcer.getElement());\n };\n\n return {\n displayNoResults,\n displayResults,\n displayResultStart,\n };\n}\n"],"names":["events.dispatch","events.eventNames"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  interface ImageType {
2
2
  url: string;
3
- altText: string;
3
+ alt?: string;
4
4
  }
5
5
  type LocationType = {
6
6
  title: string;
@@ -1 +1 @@
1
- {"version":3,"file":"_types.d.ts","sourceRoot":"","sources":["../../../source/feeds/events/_types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"_types.d.ts","sourceRoot":"","sources":["../../../source/feeds/events/_types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../source/feeds/events/grid.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAqBjD,eAAO,MAAM,UAAU,GAAI,OAAO,UAAU,KAAG,YAoB3C,CAAC"}
1
+ {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../source/feeds/events/grid.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAqBjD,eAAO,MAAM,UAAU,GAAI,OAAO,UAAU,KAAG,YAQ3C,CAAC"}
@@ -14,23 +14,14 @@ import "../../strategies/fetch/experts.js";
14
14
  import "../../strategies/fetch/inTheNews.js";
15
15
  import { gridGapLayout } from "../../strategies/layout/grid.js";
16
16
  import "@universityofmaryland/web-elements-library/layout";
17
+ import { createEventImageConfig } from "./image.js";
17
18
  const eventsGrid = (props) => createBaseFeed({
18
19
  ...props,
19
20
  enableCategoryFallback: true,
20
21
  fetchStrategy: eventsFetchStrategy,
21
22
  displayStrategy: eventsDisplayStrategy,
22
23
  layoutStrategy: gridGapLayout,
23
- imageConfig: (entry) => {
24
- const imageUrl = entry.image?.[0]?.url;
25
- const altText = entry.image?.[0]?.altText;
26
- if (!imageUrl || !altText) return null;
27
- return {
28
- imageUrl,
29
- altText,
30
- linkUrl: entry.url,
31
- linkLabel: "University of Maryland Event"
32
- };
33
- }
24
+ imageConfig: createEventImageConfig
34
25
  });
35
26
  export {
36
27
  eventsGrid
@@ -1 +1 @@
1
- {"version":3,"file":"grid.js","sources":["../../../source/feeds/events/grid.ts"],"sourcesContent":["/**\n * Events Grid Feed (Factory Pattern)\n *\n * Grid layout for event entries using the feed factory pattern.\n * This is the NEW implementation using factory + strategies.\n *\n * @module composite/events/grid-new\n */\n\nimport { createBaseFeed } from 'factory';\nimport {\n eventsFetchStrategy,\n eventsDisplayStrategy,\n gridGapLayout,\n} from 'strategies';\nimport { type BlockProps } from './_types';\nimport { type ElementModel } from '../../_types';\n\n/**\n * Create an events grid feed\n *\n * @param props - Feed configuration\n * @returns ElementModel with feed element and styles\n *\n * @example\n * ```typescript\n * const feed = eventsGrid({\n * token: 'my-token',\n * numberOfColumnsToShow: 3,\n * numberOfRowsToStart: 2,\n * isLazyLoad: true,\n * categories: ['sports', 'arts'],\n * });\n *\n * document.body.appendChild(feed.element);\n * ```\n */\nexport const eventsGrid = (props: BlockProps): ElementModel =>\n createBaseFeed({\n ...props,\n enableCategoryFallback: true,\n fetchStrategy: eventsFetchStrategy,\n displayStrategy: eventsDisplayStrategy,\n layoutStrategy: gridGapLayout,\n imageConfig: (entry) => {\n const imageUrl = entry.image?.[0]?.url;\n const altText = entry.image?.[0]?.altText;\n\n if (!imageUrl || !altText) return null;\n\n return {\n imageUrl: imageUrl,\n altText: altText,\n linkUrl: entry.url,\n linkLabel: 'University of Maryland Event',\n };\n },\n });\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAqCO,MAAM,aAAa,CAAC,UACzB,eAAe;AAAA,EACb,GAAG;AAAA,EACH,wBAAwB;AAAA,EACxB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,aAAa,CAAC,UAAU;AACtB,UAAM,WAAW,MAAM,QAAQ,CAAC,GAAG;AACnC,UAAM,UAAU,MAAM,QAAQ,CAAC,GAAG;AAElC,QAAI,CAAC,YAAY,CAAC,QAAS,QAAO;AAElC,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,SAAS,MAAM;AAAA,MACf,WAAW;AAAA,IAAA;AAAA,EAEf;AACF,CAAC;"}
1
+ {"version":3,"file":"grid.js","sources":["../../../source/feeds/events/grid.ts"],"sourcesContent":["/**\n * Events Grid Feed (Factory Pattern)\n *\n * Grid layout for event entries using the feed factory pattern.\n * This is the NEW implementation using factory + strategies.\n *\n * @module composite/events/grid-new\n */\n\nimport { createBaseFeed } from 'factory';\nimport {\n eventsFetchStrategy,\n eventsDisplayStrategy,\n gridGapLayout,\n} from 'strategies';\nimport { type BlockProps } from './_types';\nimport { createEventImageConfig } from './image';\nimport { type ElementModel } from '../../_types';\n\n/**\n * Create an events grid feed\n *\n * @param props - Feed configuration\n * @returns ElementModel with feed element and styles\n *\n * @example\n * ```typescript\n * const feed = eventsGrid({\n * token: 'my-token',\n * numberOfColumnsToShow: 3,\n * numberOfRowsToStart: 2,\n * isLazyLoad: true,\n * categories: ['sports', 'arts'],\n * });\n *\n * document.body.appendChild(feed.element);\n * ```\n */\nexport const eventsGrid = (props: BlockProps): ElementModel =>\n createBaseFeed({\n ...props,\n enableCategoryFallback: true,\n fetchStrategy: eventsFetchStrategy,\n displayStrategy: eventsDisplayStrategy,\n layoutStrategy: gridGapLayout,\n imageConfig: createEventImageConfig,\n });\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAsCO,MAAM,aAAa,CAAC,UACzB,eAAe;AAAA,EACb,GAAG;AAAA,EACH,wBAAwB;AAAA,EACxB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,aAAa;AACf,CAAC;"}
@@ -1 +1 @@
1
- {"version":3,"file":"grouped.d.ts","sourceRoot":"","sources":["../../../source/feeds/events/grouped.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAqcjD,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS,KAAG,YA+HhD,CAAC"}
1
+ {"version":3,"file":"grouped.d.ts","sourceRoot":"","sources":["../../../source/feeds/events/grouped.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAwbjD,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS,KAAG,YA+HhD,CAAC"}
@@ -16,6 +16,7 @@ import "@universityofmaryland/web-elements-library/layout";
16
16
  import { dispatch, eventNames } from "../../helpers/events/index.js";
17
17
  import { groupEventsByDate } from "../../helpers/grouping/events.js";
18
18
  import { setShadowStyles } from "../../helpers/styles/shadow.js";
19
+ import { createEventImageConfig } from "./image.js";
19
20
  const createFetchProps = (props, offset) => ({
20
21
  token: props.token,
21
22
  categories: props.categories,
@@ -23,19 +24,7 @@ const createFetchProps = (props, offset) => ({
23
24
  numberOfRowsToStart: props.numberOfRowsToStart,
24
25
  getOffset: () => offset
25
26
  });
26
- const createImageConfig = (entry) => {
27
- const imageUrl = entry.image?.[0]?.url;
28
- const altText = entry.image?.[0]?.altText;
29
- if (!imageUrl || !altText) {
30
- return null;
31
- }
32
- return {
33
- imageUrl,
34
- altText,
35
- linkUrl: entry.url,
36
- linkLabel: "University of Maryland Event"
37
- };
38
- };
27
+ const createImageConfig = (entry) => createEventImageConfig(entry);
39
28
  const createAnnouncerMessage = (count, total, isLazyLoad) => {
40
29
  return isLazyLoad ? `Showing ${count} of ${total} events` : `Showing ${count} events`;
41
30
  };
@@ -234,7 +223,8 @@ const renderGroupedEvents = async (container, events2, state, isThemeDark, isLaz
234
223
  totalEntries: state.getTotalEntries(),
235
224
  offset: state.getOffset(),
236
225
  isLazyLoad: true,
237
- callback: loadMore
226
+ callback: loadMore,
227
+ isThemeDark
238
228
  });
239
229
  const paginationElement = pagination.render(container);
240
230
  if (paginationElement) {
@@ -1 +1 @@
1
- {"version":3,"file":"grouped.js","sources":["../../../source/feeds/events/grouped.ts"],"sourcesContent":["/**\n * Events Grouped Feed (Refactored with Element Builder)\n *\n * Grouped layout for event entries organized by date with dynamic headers.\n * Uses Element Builder pattern with custom grouping logic for date-based organization.\n *\n * @module feeds/events/grouped\n */\n\nimport * as Styles from '@universityofmaryland/web-styles-library';\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport { card } from '@universityofmaryland/web-elements-library/composite';\nimport { events as eventElements } from '@universityofmaryland/web-elements-library/atomic';\nimport {\n createTextWithLink,\n createTextContainer,\n createImageOrLinkedImage,\n} from '@universityofmaryland/web-utilities-library/elements';\nimport { isExternalUrl } from '@universityofmaryland/web-utilities-library/network';\nimport { LoadingState, PaginationState, EmptyState, Announcer } from 'states';\nimport { eventsFetchStrategyRange } from 'strategies';\nimport { type EventEntry } from 'types/data';\nimport {\n grouping,\n events as eventUtilities,\n styles as styleUtilities,\n} from '../../helpers';\nimport { type ListProps } from './_types';\nimport { type ElementModel } from '../../_types';\n\n// ============================================================================\n// PURE HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Create base props for fetch strategy\n *\n * @param props - Feed props\n * @param offset - Current offset\n * @returns Base props object for strategy's composeApiVariables\n */\nconst createFetchProps = (\n props: Pick<ListProps, 'token' | 'categories' | 'numberOfRowsToStart'>,\n offset: number,\n) => ({\n token: props.token,\n categories: props.categories,\n numberOfColumnsToShow: 1,\n numberOfRowsToStart: props.numberOfRowsToStart,\n getOffset: () => offset,\n});\n\n/**\n * Create image configuration for event entry\n *\n * @param entry - Event entry\n * @returns Image config object\n */\nconst createImageConfig = (entry: EventEntry) => {\n const imageUrl = entry.image?.[0]?.url;\n const altText = entry.image?.[0]?.altText;\n\n if (!imageUrl || !altText) {\n return null;\n }\n\n return {\n imageUrl: imageUrl,\n altText: altText,\n linkUrl: entry.url,\n linkLabel: 'University of Maryland Event',\n };\n};\n\n/**\n * Create announcer message\n *\n * @param count - Number of events shown\n * @param total - Total events\n * @param isLazyLoad - Lazy load enabled\n * @returns Announcer message\n */\nconst createAnnouncerMessage = (\n count: number,\n total: number,\n isLazyLoad: boolean,\n): string => {\n return isLazyLoad\n ? `Showing ${count} of ${total} events`\n : `Showing ${count} events`;\n};\n\n// ============================================================================\n// STATE MANAGER CLASS\n// ============================================================================\n\n/**\n * Manages grouped feed state and shadow DOM synchronization\n *\n * Encapsulates all mutable state including offset, total,\n * last date headline tracking, and shadow DOM management.\n */\nclass GroupedFeedState {\n private stylesArray: string[] = [];\n private shadowRoot: ShadowRoot | null = null;\n private totalEntries: number = 0;\n private offset: number = 0;\n private lastDateHeadline: string | null = null;\n\n /**\n * Initialize state with initial styles\n *\n * @param initialStyles - Initial CSS styles\n */\n constructor(initialStyles: string) {\n this.stylesArray.push(initialStyles);\n }\n\n /**\n * Add styles to the accumulated styles\n *\n * @param styles - CSS styles to add\n */\n addStyles(styles: string): void {\n this.stylesArray.push(styles);\n }\n\n /**\n * Set shadow root reference for style updates\n *\n * @param shadow - Shadow root element\n */\n setShadowRoot(shadow: ShadowRoot): void {\n this.shadowRoot = shadow;\n }\n\n /**\n * Update shadow DOM styles\n *\n * @returns Promise that resolves when styles are updated\n */\n async updateShadowStyles(): Promise<void> {\n if (!this.shadowRoot) return;\n await styleUtilities.setShadowStyles({\n shadowRoot: this.shadowRoot,\n styles: this.getStyles(),\n });\n }\n\n /**\n * Get accumulated styles as single string\n *\n * @returns Combined CSS styles\n */\n getStyles(): string {\n return this.stylesArray.join('\\n');\n }\n\n /**\n * Get shadow root callback for events\n *\n * @returns Callback function for shadow root\n */\n getShadowCallback(): (shadow: ShadowRoot) => void {\n return (shadow) => this.setShadowRoot(shadow);\n }\n\n /**\n * Get current offset\n *\n * @returns Current offset\n */\n getOffset(): number {\n return this.offset;\n }\n\n /**\n * Increment offset by count\n *\n * @param count - Number to increment by\n */\n incrementOffset(count: number): void {\n this.offset += count;\n }\n\n /**\n * Get total entries\n *\n * @returns Total entries\n */\n getTotalEntries(): number {\n return this.totalEntries;\n }\n\n /**\n * Set total entries\n *\n * @param total - Total entries\n */\n setTotalEntries(total: number): void {\n this.totalEntries = total;\n }\n\n /**\n * Get last date headline\n *\n * @returns Last date headline or null\n */\n getLastDateHeadline(): string | null {\n return this.lastDateHeadline;\n }\n\n /**\n * Set last date headline\n *\n * @param headline - Date headline\n */\n setLastDateHeadline(headline: string): void {\n this.lastDateHeadline = headline;\n }\n}\n\n// ============================================================================\n// RENDERING FUNCTIONS\n// ============================================================================\n\n/**\n * Create date header element\n *\n * @param date - Date string\n * @param state - State manager\n * @returns Date header element model\n */\nconst createDateHeader = (\n date: string,\n state: GroupedFeedState,\n): ElementModel => {\n const dateHeadline = document.createElement('p');\n dateHeadline.textContent = date;\n\n const headerElement = new ElementBuilder(dateHeadline)\n .styled(Styles.element.text.decoration.ribbon)\n .withStyles({\n element: {\n margin: `${Styles.token.spacing.lg} 0`,\n },\n })\n .build();\n\n state.addStyles(headerElement.styles);\n return headerElement;\n};\n\n/**\n * Create event card for grouped display\n *\n * @param entry - Event entry\n * @param isThemeDark - Dark theme flag\n * @returns Event card element model\n */\nconst createEventCard = (\n entry: EventEntry,\n isThemeDark: boolean,\n): ElementModel => {\n const openInNewTab = isExternalUrl(entry.url);\n const imageData = createImageConfig(entry);\n const image =\n imageData && imageData.imageUrl && imageData.altText\n ? createImageOrLinkedImage({ ...imageData, openInNewTab })\n : null;\n\n return card.list({\n headline: createTextWithLink({ text: entry.title, url: entry.url, openInNewTab }),\n text: createTextContainer({ text: entry.summary, allowHTML: true }),\n dateSign: eventElements.sign({\n startMonth: entry.startMonth,\n startDay: entry.startDay,\n endMonth: entry.endMonth,\n endDay: entry.endDay,\n isThemeDark,\n isLargeSize: true,\n }),\n eventMeta: eventElements.meta({\n ...entry,\n isThemeDark,\n } as any),\n image: image,\n isAligned: false,\n isThemeDark,\n });\n};\n\n/**\n * Create group container with entries\n *\n * @param events - Event entries for this group\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @returns Group container element model\n */\nconst createGroupContainer = (\n events: EventEntry[],\n state: GroupedFeedState,\n isThemeDark: boolean,\n): ElementModel => {\n const entriesBuilder = new ElementBuilder('div')\n .withClassName('umd-feed-events-grouped-entries')\n .withStyles({\n element: {\n [`&:not(:last-of-type)`]: {\n paddingBottom: Styles.token.spacing.md,\n borderBottom: `1px solid ${\n isThemeDark\n ? Styles.token.color.gray.dark\n : Styles.token.color.gray.light\n }`,\n },\n [`& + .umd-feed-events-grouped-entries`]: {\n paddingTop: Styles.token.spacing.md,\n marginTop: Styles.token.spacing.md,\n borderTop: `none`,\n },\n [`& > *:not(:last-child)`]: {\n paddingBottom: Styles.token.spacing.md,\n marginBottom: Styles.token.spacing.md,\n borderBottom: `1px solid ${\n isThemeDark\n ? Styles.token.color.gray.dark\n : Styles.token.color.gray.light\n }`,\n },\n },\n });\n\n // Create cards for each event\n events.forEach((entry) => {\n const eventCard = createEventCard(entry, isThemeDark);\n entriesBuilder.withChild(eventCard);\n state.addStyles(eventCard.styles);\n });\n\n const groupElement = entriesBuilder.build();\n state.addStyles(groupElement.styles);\n\n return groupElement;\n};\n\n/**\n * Render grouped events\n *\n * @param container - Container element\n * @param events - Event entries\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @param isLazyLoad - Lazy load enabled\n * @param loadMore - Load more callback\n * @returns Promise that resolves when rendering is complete\n */\nconst renderGroupedEvents = async (\n container: HTMLElement,\n events: EventEntry[],\n state: GroupedFeedState,\n isThemeDark: boolean,\n isLazyLoad: boolean,\n loadMore: () => Promise<void>,\n): Promise<void> => {\n const grid = container.querySelector(\n '#umd-feed-events-grouped-container',\n ) as HTMLElement;\n if (!grid) return;\n\n // Remove existing states\n container.querySelector('.umd-loader-container')?.remove();\n container\n .querySelector(`.${Styles.layout.alignment.block.center.className}`)\n ?.remove();\n\n // Group events by date\n const groupedEvents = grouping.groupEventsByDate(events);\n let actualEventCount = 0;\n\n groupedEvents.forEach((group) => {\n // Add date header if it's a new date\n if (group.date !== state.getLastDateHeadline()) {\n const headerElement = createDateHeader(group.date, state);\n grid.appendChild(headerElement.element);\n state.setLastDateHeadline(group.date);\n }\n\n // Create group container with entries\n const groupElement = createGroupContainer(group.events, state, isThemeDark);\n grid.appendChild(groupElement.element);\n\n actualEventCount += group.events.length;\n });\n\n // Update offset with actual event count\n state.incrementOffset(actualEventCount);\n\n // Add pagination if lazy load enabled\n if (isLazyLoad && state.getTotalEntries() > state.getOffset()) {\n const pagination = new PaginationState({\n totalEntries: state.getTotalEntries(),\n offset: state.getOffset(),\n isLazyLoad: true,\n callback: loadMore,\n });\n\n const paginationElement = pagination.render(container);\n if (paginationElement) {\n state.addStyles(paginationElement.styles);\n }\n }\n\n // Update shadow root styles\n await state.updateShadowStyles();\n};\n\n/**\n * Render error state\n *\n * @param container - Container element\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @returns Promise that resolves when rendering is complete\n */\nconst renderError = async (\n container: HTMLElement,\n state: GroupedFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const emptyState = new EmptyState({\n message: 'No events found',\n isThemeDark,\n });\n emptyState.render(container);\n state.addStyles(emptyState.styles);\n\n const announcer = new Announcer({ message: 'No events found' });\n container.appendChild(announcer.getElement());\n\n eventUtilities.dispatch(container, eventUtilities.eventNames.FEED_ERROR, {\n error: 'No results found',\n });\n\n await state.updateShadowStyles();\n};\n\n// ============================================================================\n// MAIN EXPORT\n// ============================================================================\n\n/**\n * Create a grouped events feed\n *\n * Groups events by date with dynamic date headers (Today, day names, formatted dates).\n * Events are sorted by priority within each group (multi-day events starting today first).\n * Uses Element Builder pattern for clean construction.\n *\n * @param props - Feed configuration\n * @returns ElementModel with feed element and styles\n *\n * @example\n * ```typescript\n * const feed = eventsGrouped({\n * token: 'my-token',\n * numberOfRowsToStart: 10,\n * isLazyLoad: true,\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With dark theme\n * const feed = eventsGrouped({\n * token: 'my-token',\n * numberOfRowsToStart: 20,\n * isThemeDark: true,\n * });\n * ```\n */\nexport const eventsGrouped = (props: ListProps): ElementModel => {\n const {\n token,\n isThemeDark = false,\n isLazyLoad = false,\n numberOfRowsToStart,\n categories,\n } = props;\n\n // Create container using ElementBuilder\n const containerBuilder = new ElementBuilder('div').withClassName(\n 'events-grouped-feed',\n );\n\n // Get element for manipulation (non-destructive)\n const container = containerBuilder.getElement();\n\n // Initialize state management\n const loading = new LoadingState({ isThemeDark });\n const state = new GroupedFeedState(loading.styles);\n\n // Create layout\n const layoutElement = new ElementBuilder('div')\n .withClassName('umd-feed-events-grouped')\n .build();\n state.addStyles(layoutElement.styles);\n\n /**\n * Load more events (for lazy loading)\n */\n const loadMore = async (): Promise<void> => {\n const fetchProps = createFetchProps(\n { token, categories, numberOfRowsToStart },\n state.getOffset(),\n );\n const variables = eventsFetchStrategyRange.composeApiVariables(fetchProps);\n\n const entries = await eventsFetchStrategyRange.fetchEntries(variables);\n if (entries && entries.length > 0) {\n await renderGroupedEvents(\n container,\n entries,\n state,\n isThemeDark,\n isLazyLoad,\n loadMore,\n );\n }\n };\n\n /**\n * Initialize feed\n */\n const initialize = async (): Promise<void> => {\n loading.show(container);\n\n const fetchProps = createFetchProps(\n { token, categories, numberOfRowsToStart },\n 0,\n );\n const variables = eventsFetchStrategyRange.composeApiVariables(fetchProps);\n\n // Fetch count and entries\n const [count, entries] = await Promise.all([\n eventsFetchStrategyRange.fetchCount(variables),\n eventsFetchStrategyRange.fetchEntries(variables),\n ]);\n\n // Remove loading\n loading.hide();\n\n // Handle no results\n if (!entries || entries.length === 0) {\n await renderError(container, state, isThemeDark);\n return;\n }\n\n // Set total and append layout\n state.setTotalEntries(count || entries.length);\n layoutElement.element.setAttribute(\n 'id',\n 'umd-feed-events-grouped-container',\n );\n container.appendChild(layoutElement.element);\n\n // Dispatch loaded event\n eventUtilities.dispatch(container, eventUtilities.eventNames.FEED_LOADED, {\n items: entries,\n count: entries.length,\n total: state.getTotalEntries(),\n });\n\n // Render grouped events\n await renderGroupedEvents(\n container,\n entries,\n state,\n isThemeDark,\n isLazyLoad,\n loadMore,\n );\n\n // Add announcer\n const message = createAnnouncerMessage(\n entries.length,\n state.getTotalEntries(),\n isLazyLoad,\n );\n const announcer = new Announcer({ message });\n container.appendChild(announcer.getElement());\n };\n\n // Start initialization\n initialize();\n\n // Build and return element model\n const model = containerBuilder.build();\n\n return {\n element: model.element,\n get styles() {\n return state.getStyles();\n },\n events: {\n callback: state.getShadowCallback(),\n },\n };\n};\n"],"names":["styleUtilities.setShadowStyles","eventElements","events","grouping.groupEventsByDate","eventUtilities.dispatch","eventUtilities.eventNames"],"mappings":"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"}
1
+ {"version":3,"file":"grouped.js","sources":["../../../source/feeds/events/grouped.ts"],"sourcesContent":["/**\n * Events Grouped Feed (Refactored with Element Builder)\n *\n * Grouped layout for event entries organized by date with dynamic headers.\n * Uses Element Builder pattern with custom grouping logic for date-based organization.\n *\n * @module feeds/events/grouped\n */\n\nimport * as Styles from '@universityofmaryland/web-styles-library';\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport { card } from '@universityofmaryland/web-elements-library/composite';\nimport { events as eventElements } from '@universityofmaryland/web-elements-library/atomic';\nimport {\n createTextWithLink,\n createTextContainer,\n createImageOrLinkedImage,\n} from '@universityofmaryland/web-utilities-library/elements';\nimport { isExternalUrl } from '@universityofmaryland/web-utilities-library/network';\nimport { LoadingState, PaginationState, EmptyState, Announcer } from 'states';\nimport { eventsFetchStrategyRange } from 'strategies';\nimport { type EventEntry } from 'types/data';\nimport {\n grouping,\n events as eventUtilities,\n styles as styleUtilities,\n} from '../../helpers';\nimport { type ListProps } from './_types';\nimport { createEventImageConfig } from './image';\nimport { type ElementModel } from '../../_types';\n\n// ============================================================================\n// PURE HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Create base props for fetch strategy\n *\n * @param props - Feed props\n * @param offset - Current offset\n * @returns Base props object for strategy's composeApiVariables\n */\nconst createFetchProps = (\n props: Pick<ListProps, 'token' | 'categories' | 'numberOfRowsToStart'>,\n offset: number,\n) => ({\n token: props.token,\n categories: props.categories,\n numberOfColumnsToShow: 1,\n numberOfRowsToStart: props.numberOfRowsToStart,\n getOffset: () => offset,\n});\n\n/**\n * Create image configuration for event entry\n *\n * @param entry - Event entry\n * @returns Image config object\n */\nconst createImageConfig = (entry: EventEntry) => createEventImageConfig(entry);\n\n/**\n * Create announcer message\n *\n * @param count - Number of events shown\n * @param total - Total events\n * @param isLazyLoad - Lazy load enabled\n * @returns Announcer message\n */\nconst createAnnouncerMessage = (\n count: number,\n total: number,\n isLazyLoad: boolean,\n): string => {\n return isLazyLoad\n ? `Showing ${count} of ${total} events`\n : `Showing ${count} events`;\n};\n\n// ============================================================================\n// STATE MANAGER CLASS\n// ============================================================================\n\n/**\n * Manages grouped feed state and shadow DOM synchronization\n *\n * Encapsulates all mutable state including offset, total,\n * last date headline tracking, and shadow DOM management.\n */\nclass GroupedFeedState {\n private stylesArray: string[] = [];\n private shadowRoot: ShadowRoot | null = null;\n private totalEntries: number = 0;\n private offset: number = 0;\n private lastDateHeadline: string | null = null;\n\n /**\n * Initialize state with initial styles\n *\n * @param initialStyles - Initial CSS styles\n */\n constructor(initialStyles: string) {\n this.stylesArray.push(initialStyles);\n }\n\n /**\n * Add styles to the accumulated styles\n *\n * @param styles - CSS styles to add\n */\n addStyles(styles: string): void {\n this.stylesArray.push(styles);\n }\n\n /**\n * Set shadow root reference for style updates\n *\n * @param shadow - Shadow root element\n */\n setShadowRoot(shadow: ShadowRoot): void {\n this.shadowRoot = shadow;\n }\n\n /**\n * Update shadow DOM styles\n *\n * @returns Promise that resolves when styles are updated\n */\n async updateShadowStyles(): Promise<void> {\n if (!this.shadowRoot) return;\n await styleUtilities.setShadowStyles({\n shadowRoot: this.shadowRoot,\n styles: this.getStyles(),\n });\n }\n\n /**\n * Get accumulated styles as single string\n *\n * @returns Combined CSS styles\n */\n getStyles(): string {\n return this.stylesArray.join('\\n');\n }\n\n /**\n * Get shadow root callback for events\n *\n * @returns Callback function for shadow root\n */\n getShadowCallback(): (shadow: ShadowRoot) => void {\n return (shadow) => this.setShadowRoot(shadow);\n }\n\n /**\n * Get current offset\n *\n * @returns Current offset\n */\n getOffset(): number {\n return this.offset;\n }\n\n /**\n * Increment offset by count\n *\n * @param count - Number to increment by\n */\n incrementOffset(count: number): void {\n this.offset += count;\n }\n\n /**\n * Get total entries\n *\n * @returns Total entries\n */\n getTotalEntries(): number {\n return this.totalEntries;\n }\n\n /**\n * Set total entries\n *\n * @param total - Total entries\n */\n setTotalEntries(total: number): void {\n this.totalEntries = total;\n }\n\n /**\n * Get last date headline\n *\n * @returns Last date headline or null\n */\n getLastDateHeadline(): string | null {\n return this.lastDateHeadline;\n }\n\n /**\n * Set last date headline\n *\n * @param headline - Date headline\n */\n setLastDateHeadline(headline: string): void {\n this.lastDateHeadline = headline;\n }\n}\n\n// ============================================================================\n// RENDERING FUNCTIONS\n// ============================================================================\n\n/**\n * Create date header element\n *\n * @param date - Date string\n * @param state - State manager\n * @returns Date header element model\n */\nconst createDateHeader = (\n date: string,\n state: GroupedFeedState,\n): ElementModel => {\n const dateHeadline = document.createElement('p');\n dateHeadline.textContent = date;\n\n const headerElement = new ElementBuilder(dateHeadline)\n .styled(Styles.element.text.decoration.ribbon)\n .withStyles({\n element: {\n margin: `${Styles.token.spacing.lg} 0`,\n },\n })\n .build();\n\n state.addStyles(headerElement.styles);\n return headerElement;\n};\n\n/**\n * Create event card for grouped display\n *\n * @param entry - Event entry\n * @param isThemeDark - Dark theme flag\n * @returns Event card element model\n */\nconst createEventCard = (\n entry: EventEntry,\n isThemeDark: boolean,\n): ElementModel => {\n const openInNewTab = isExternalUrl(entry.url);\n const imageData = createImageConfig(entry);\n const image =\n imageData && imageData.imageUrl && imageData.altText\n ? createImageOrLinkedImage({ ...imageData, openInNewTab })\n : null;\n\n return card.list({\n headline: createTextWithLink({ text: entry.title, url: entry.url, openInNewTab }),\n text: createTextContainer({ text: entry.summary, allowHTML: true }),\n dateSign: eventElements.sign({\n startMonth: entry.startMonth,\n startDay: entry.startDay,\n endMonth: entry.endMonth,\n endDay: entry.endDay,\n isThemeDark,\n isLargeSize: true,\n }),\n eventMeta: eventElements.meta({\n ...entry,\n isThemeDark,\n } as any),\n image: image,\n isAligned: false,\n isThemeDark,\n });\n};\n\n/**\n * Create group container with entries\n *\n * @param events - Event entries for this group\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @returns Group container element model\n */\nconst createGroupContainer = (\n events: EventEntry[],\n state: GroupedFeedState,\n isThemeDark: boolean,\n): ElementModel => {\n const entriesBuilder = new ElementBuilder('div')\n .withClassName('umd-feed-events-grouped-entries')\n .withStyles({\n element: {\n [`&:not(:last-of-type)`]: {\n paddingBottom: Styles.token.spacing.md,\n borderBottom: `1px solid ${\n isThemeDark\n ? Styles.token.color.gray.dark\n : Styles.token.color.gray.light\n }`,\n },\n [`& + .umd-feed-events-grouped-entries`]: {\n paddingTop: Styles.token.spacing.md,\n marginTop: Styles.token.spacing.md,\n borderTop: `none`,\n },\n [`& > *:not(:last-child)`]: {\n paddingBottom: Styles.token.spacing.md,\n marginBottom: Styles.token.spacing.md,\n borderBottom: `1px solid ${\n isThemeDark\n ? Styles.token.color.gray.dark\n : Styles.token.color.gray.light\n }`,\n },\n },\n });\n\n // Create cards for each event\n events.forEach((entry) => {\n const eventCard = createEventCard(entry, isThemeDark);\n entriesBuilder.withChild(eventCard);\n state.addStyles(eventCard.styles);\n });\n\n const groupElement = entriesBuilder.build();\n state.addStyles(groupElement.styles);\n\n return groupElement;\n};\n\n/**\n * Render grouped events\n *\n * @param container - Container element\n * @param events - Event entries\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @param isLazyLoad - Lazy load enabled\n * @param loadMore - Load more callback\n * @returns Promise that resolves when rendering is complete\n */\nconst renderGroupedEvents = async (\n container: HTMLElement,\n events: EventEntry[],\n state: GroupedFeedState,\n isThemeDark: boolean,\n isLazyLoad: boolean,\n loadMore: () => Promise<void>,\n): Promise<void> => {\n const grid = container.querySelector(\n '#umd-feed-events-grouped-container',\n ) as HTMLElement;\n if (!grid) return;\n\n // Remove existing states\n container.querySelector('.umd-loader-container')?.remove();\n container\n .querySelector(`.${Styles.layout.alignment.block.center.className}`)\n ?.remove();\n\n // Group events by date\n const groupedEvents = grouping.groupEventsByDate(events);\n let actualEventCount = 0;\n\n groupedEvents.forEach((group) => {\n // Add date header if it's a new date\n if (group.date !== state.getLastDateHeadline()) {\n const headerElement = createDateHeader(group.date, state);\n grid.appendChild(headerElement.element);\n state.setLastDateHeadline(group.date);\n }\n\n // Create group container with entries\n const groupElement = createGroupContainer(group.events, state, isThemeDark);\n grid.appendChild(groupElement.element);\n\n actualEventCount += group.events.length;\n });\n\n // Update offset with actual event count\n state.incrementOffset(actualEventCount);\n\n // Add pagination if lazy load enabled\n if (isLazyLoad && state.getTotalEntries() > state.getOffset()) {\n const pagination = new PaginationState({\n totalEntries: state.getTotalEntries(),\n offset: state.getOffset(),\n isLazyLoad: true,\n callback: loadMore,\n isThemeDark,\n });\n\n const paginationElement = pagination.render(container);\n if (paginationElement) {\n state.addStyles(paginationElement.styles);\n }\n }\n\n // Update shadow root styles\n await state.updateShadowStyles();\n};\n\n/**\n * Render error state\n *\n * @param container - Container element\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @returns Promise that resolves when rendering is complete\n */\nconst renderError = async (\n container: HTMLElement,\n state: GroupedFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const emptyState = new EmptyState({\n message: 'No events found',\n isThemeDark,\n });\n emptyState.render(container);\n state.addStyles(emptyState.styles);\n\n const announcer = new Announcer({ message: 'No events found' });\n container.appendChild(announcer.getElement());\n\n eventUtilities.dispatch(container, eventUtilities.eventNames.FEED_ERROR, {\n error: 'No results found',\n });\n\n await state.updateShadowStyles();\n};\n\n// ============================================================================\n// MAIN EXPORT\n// ============================================================================\n\n/**\n * Create a grouped events feed\n *\n * Groups events by date with dynamic date headers (Today, day names, formatted dates).\n * Events are sorted by priority within each group (multi-day events starting today first).\n * Uses Element Builder pattern for clean construction.\n *\n * @param props - Feed configuration\n * @returns ElementModel with feed element and styles\n *\n * @example\n * ```typescript\n * const feed = eventsGrouped({\n * token: 'my-token',\n * numberOfRowsToStart: 10,\n * isLazyLoad: true,\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With dark theme\n * const feed = eventsGrouped({\n * token: 'my-token',\n * numberOfRowsToStart: 20,\n * isThemeDark: true,\n * });\n * ```\n */\nexport const eventsGrouped = (props: ListProps): ElementModel => {\n const {\n token,\n isThemeDark = false,\n isLazyLoad = false,\n numberOfRowsToStart,\n categories,\n } = props;\n\n // Create container using ElementBuilder\n const containerBuilder = new ElementBuilder('div').withClassName(\n 'events-grouped-feed',\n );\n\n // Get element for manipulation (non-destructive)\n const container = containerBuilder.getElement();\n\n // Initialize state management\n const loading = new LoadingState({ isThemeDark });\n const state = new GroupedFeedState(loading.styles);\n\n // Create layout\n const layoutElement = new ElementBuilder('div')\n .withClassName('umd-feed-events-grouped')\n .build();\n state.addStyles(layoutElement.styles);\n\n /**\n * Load more events (for lazy loading)\n */\n const loadMore = async (): Promise<void> => {\n const fetchProps = createFetchProps(\n { token, categories, numberOfRowsToStart },\n state.getOffset(),\n );\n const variables = eventsFetchStrategyRange.composeApiVariables(fetchProps);\n\n const entries = await eventsFetchStrategyRange.fetchEntries(variables);\n if (entries && entries.length > 0) {\n await renderGroupedEvents(\n container,\n entries,\n state,\n isThemeDark,\n isLazyLoad,\n loadMore,\n );\n }\n };\n\n /**\n * Initialize feed\n */\n const initialize = async (): Promise<void> => {\n loading.show(container);\n\n const fetchProps = createFetchProps(\n { token, categories, numberOfRowsToStart },\n 0,\n );\n const variables = eventsFetchStrategyRange.composeApiVariables(fetchProps);\n\n // Fetch count and entries\n const [count, entries] = await Promise.all([\n eventsFetchStrategyRange.fetchCount(variables),\n eventsFetchStrategyRange.fetchEntries(variables),\n ]);\n\n // Remove loading\n loading.hide();\n\n // Handle no results\n if (!entries || entries.length === 0) {\n await renderError(container, state, isThemeDark);\n return;\n }\n\n // Set total and append layout\n state.setTotalEntries(count || entries.length);\n layoutElement.element.setAttribute(\n 'id',\n 'umd-feed-events-grouped-container',\n );\n container.appendChild(layoutElement.element);\n\n // Dispatch loaded event\n eventUtilities.dispatch(container, eventUtilities.eventNames.FEED_LOADED, {\n items: entries,\n count: entries.length,\n total: state.getTotalEntries(),\n });\n\n // Render grouped events\n await renderGroupedEvents(\n container,\n entries,\n state,\n isThemeDark,\n isLazyLoad,\n loadMore,\n );\n\n // Add announcer\n const message = createAnnouncerMessage(\n entries.length,\n state.getTotalEntries(),\n isLazyLoad,\n );\n const announcer = new Announcer({ message });\n container.appendChild(announcer.getElement());\n };\n\n // Start initialization\n initialize();\n\n // Build and return element model\n const model = containerBuilder.build();\n\n return {\n element: model.element,\n get styles() {\n return state.getStyles();\n },\n events: {\n callback: state.getShadowCallback(),\n },\n };\n};\n"],"names":["styleUtilities.setShadowStyles","eventElements","events","grouping.groupEventsByDate","eventUtilities.dispatch","eventUtilities.eventNames"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { EventEntry } from '../../types/data';
2
+ export declare const createEventImageConfig: (entry: Pick<EventEntry, "image" | "url">) => {
3
+ imageUrl: string;
4
+ altText: string;
5
+ linkUrl: string;
6
+ linkLabel: string;
7
+ } | null;
8
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../source/feeds/events/image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,sBAAsB,GACjC,OAAO,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,KAAK,CAAC;;;;;QAezC,CAAC"}
@@ -0,0 +1,17 @@
1
+ const createEventImageConfig = (entry) => {
2
+ const imageUrl = entry.image?.[0]?.url;
3
+ const altText = entry.image?.[0]?.alt;
4
+ if (!imageUrl || !altText) {
5
+ return null;
6
+ }
7
+ return {
8
+ imageUrl,
9
+ altText,
10
+ linkUrl: entry.url,
11
+ linkLabel: "University of Maryland Event"
12
+ };
13
+ };
14
+ export {
15
+ createEventImageConfig
16
+ };
17
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","sources":["../../../source/feeds/events/image.ts"],"sourcesContent":["import { type EventEntry } from 'types/data';\n\nexport const createEventImageConfig = (\n entry: Pick<EventEntry, 'image' | 'url'>,\n) => {\n const imageUrl = entry.image?.[0]?.url;\n const altText = entry.image?.[0]?.alt;\n\n if (!imageUrl || !altText) {\n return null;\n }\n\n return {\n imageUrl,\n altText,\n linkUrl: entry.url,\n linkLabel: 'University of Maryland Event',\n };\n};\n"],"names":[],"mappings":"AAEO,MAAM,yBAAyB,CACpC,UACG;AACH,QAAM,WAAW,MAAM,QAAQ,CAAC,GAAG;AACnC,QAAM,UAAU,MAAM,QAAQ,CAAC,GAAG;AAElC,MAAI,CAAC,YAAY,CAAC,SAAS;AACzB,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,MAAM;AAAA,IACf,WAAW;AAAA,EAAA;AAEf;"}
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../source/feeds/events/list.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAoBjD,eAAO,MAAM,UAAU,GAAI,OAAO,SAAS,KAAG,YAuB1C,CAAC"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../source/feeds/events/list.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAoBjD,eAAO,MAAM,UAAU,GAAI,OAAO,SAAS,KAAG,YAS1C,CAAC"}
@@ -14,6 +14,7 @@ import "../../strategies/fetch/experts.js";
14
14
  import "../../strategies/fetch/inTheNews.js";
15
15
  import { stackedLayout } from "../../strategies/layout/grid.js";
16
16
  import "@universityofmaryland/web-elements-library/layout";
17
+ import { createEventImageConfig } from "./image.js";
17
18
  const eventsList = (props) => createBaseFeed({
18
19
  ...props,
19
20
  enableCategoryFallback: true,
@@ -21,19 +22,7 @@ const eventsList = (props) => createBaseFeed({
21
22
  fetchStrategy: eventsFetchStrategy,
22
23
  displayStrategy: eventsDisplayStrategy,
23
24
  layoutStrategy: stackedLayout,
24
- imageConfig: (entry) => {
25
- const imageUrl = entry.image?.[0]?.url;
26
- const altText = entry.image?.[0]?.altText;
27
- if (!imageUrl || !altText) {
28
- return null;
29
- }
30
- return {
31
- imageUrl,
32
- altText,
33
- linkUrl: entry.url,
34
- linkLabel: "University of Maryland Event"
35
- };
36
- }
25
+ imageConfig: createEventImageConfig
37
26
  });
38
27
  export {
39
28
  eventsList
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","sources":["../../../source/feeds/events/list.ts"],"sourcesContent":["/**\n * Events List Feed (Factory Pattern)\n *\n * List layout for event entries using the feed factory pattern.\n * This is the NEW implementation using factory + strategies.\n *\n * @module composite/events/list-new\n */\n\nimport { createBaseFeed } from 'factory';\nimport {\n eventsFetchStrategy,\n eventsDisplayStrategy,\n stackedLayout,\n} from 'strategies';\nimport { type ListProps } from './_types';\nimport { type ElementModel } from '../../_types';\n\n/**\n * Create an events list feed\n *\n * @param props - Feed configuration\n * @returns ElementModel with feed element and styles\n *\n * @example\n * ```typescript\n * const feed = eventsList({\n * token: 'my-token',\n * numberOfRowsToStart: 5,\n * isLazyLoad: true,\n * categories: ['sports', 'arts'],\n * });\n *\n * document.body.appendChild(feed.element);\n * ```\n */\nexport const eventsList = (props: ListProps): ElementModel =>\n createBaseFeed({\n ...props,\n enableCategoryFallback: true,\n cardType: 'list',\n fetchStrategy: eventsFetchStrategy,\n displayStrategy: eventsDisplayStrategy,\n layoutStrategy: stackedLayout,\n imageConfig: (entry) => {\n const imageUrl = entry.image?.[0]?.url;\n const altText = entry.image?.[0]?.altText;\n\n if (!imageUrl || !altText) {\n return null;\n }\n\n return {\n imageUrl: imageUrl,\n altText: altText,\n linkUrl: entry.url,\n linkLabel: 'University of Maryland Event',\n };\n },\n });\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAoCO,MAAM,aAAa,CAAC,UACzB,eAAe;AAAA,EACb,GAAG;AAAA,EACH,wBAAwB;AAAA,EACxB,UAAU;AAAA,EACV,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,aAAa,CAAC,UAAU;AACtB,UAAM,WAAW,MAAM,QAAQ,CAAC,GAAG;AACnC,UAAM,UAAU,MAAM,QAAQ,CAAC,GAAG;AAElC,QAAI,CAAC,YAAY,CAAC,SAAS;AACzB,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,SAAS,MAAM;AAAA,MACf,WAAW;AAAA,IAAA;AAAA,EAEf;AACF,CAAC;"}
1
+ {"version":3,"file":"list.js","sources":["../../../source/feeds/events/list.ts"],"sourcesContent":["/**\n * Events List Feed (Factory Pattern)\n *\n * List layout for event entries using the feed factory pattern.\n * This is the NEW implementation using factory + strategies.\n *\n * @module composite/events/list-new\n */\n\nimport { createBaseFeed } from 'factory';\nimport {\n eventsFetchStrategy,\n eventsDisplayStrategy,\n stackedLayout,\n} from 'strategies';\nimport { type ListProps } from './_types';\nimport { createEventImageConfig } from './image';\nimport { type ElementModel } from '../../_types';\n\n/**\n * Create an events list feed\n *\n * @param props - Feed configuration\n * @returns ElementModel with feed element and styles\n *\n * @example\n * ```typescript\n * const feed = eventsList({\n * token: 'my-token',\n * numberOfRowsToStart: 5,\n * isLazyLoad: true,\n * categories: ['sports', 'arts'],\n * });\n *\n * document.body.appendChild(feed.element);\n * ```\n */\nexport const eventsList = (props: ListProps): ElementModel =>\n createBaseFeed({\n ...props,\n enableCategoryFallback: true,\n cardType: 'list',\n fetchStrategy: eventsFetchStrategy,\n displayStrategy: eventsDisplayStrategy,\n layoutStrategy: stackedLayout,\n imageConfig: createEventImageConfig,\n });\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAqCO,MAAM,aAAa,CAAC,UACzB,eAAe;AAAA,EACb,GAAG;AAAA,EACH,wBAAwB;AAAA,EACxB,UAAU;AAAA,EACV,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,aAAa;AACf,CAAC;"}
@@ -1 +1 @@
1
- {"version":3,"file":"in-the-news.d.ts","sourceRoot":"","sources":["../../../source/feeds/experts/in-the-news.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AA8SjD,eAAO,MAAM,gBAAgB,GAAI,OAAO,cAAc,KAAG,YAqJxD,CAAC"}
1
+ {"version":3,"file":"in-the-news.d.ts","sourceRoot":"","sources":["../../../source/feeds/experts/in-the-news.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AA+SjD,eAAO,MAAM,gBAAgB,GAAI,OAAO,cAAc,KAAG,YAqJxD,CAAC"}
@@ -152,7 +152,8 @@ const renderLayout = async (container, expert, newsEntries, state, props, loadMo
152
152
  totalEntries: state.getTotalEntries(),
153
153
  offset: state.getOffset(),
154
154
  isLazyLoad: true,
155
- callback: loadMore
155
+ callback: loadMore,
156
+ isThemeDark
156
157
  });
157
158
  const paginationElement = pagination.render(rightColumn);
158
159
  if (paginationElement) state.addStyles(paginationElement.styles);
@@ -1 +1 @@
1
- {"version":3,"file":"in-the-news.js","sources":["../../../source/feeds/experts/in-the-news.ts"],"sourcesContent":["import { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport * as token from '@universityofmaryland/web-token-library';\nimport { card } from '@universityofmaryland/web-elements-library/composite';\nimport { gridOffset, stacked } from '@universityofmaryland/web-elements-library/layout';\nimport {\n LoadingState,\n PaginationState,\n EmptyState,\n Announcer,\n} from '../../states';\nimport { expertsFetchStrategy } from '../../strategies/fetch/experts';\nimport { inTheNewsFetchStrategy } from '../../strategies/fetch/inTheNews';\nimport { expertsDisplayStrategy } from '../../strategies/display/experts';\nimport { inTheNewsDisplayStrategy } from '../../strategies/display/inTheNews';\nimport {\n events as eventUtilities,\n styles as styleUtilities,\n} from '../../helpers';\nimport { type InTheNewsProps } from './_types';\nimport { type ElementModel } from '../../_types';\nimport { type ExpertEntry, type InTheNewsEntry } from 'types/data';\n\nconst DEFAULT_ROWS_TO_START = 5;\nconst MIN_ROWS_TO_START = 2;\nconst MAX_ROWS_TO_START = 10;\nconst LOAD_MORE_ITEMS = 5;\n\nconst normalizeRowCount = (rows?: number): number => {\n if (rows === undefined) return DEFAULT_ROWS_TO_START;\n return Math.max(MIN_ROWS_TO_START, Math.min(MAX_ROWS_TO_START, rows));\n};\n\nconst createExpertFetchProps = (token: string, expertId: string) => ({\n token,\n limit: 1,\n offset: 0,\n ids: [expertId],\n});\n\nconst createNewsFetchProps = (\n token: string,\n expertId: string,\n numberOfRowsToStart: number,\n offset: number,\n) => ({\n token,\n expertId,\n numberOfRowsToStart,\n getOffset: () => offset,\n});\n\nconst createAnnouncerMessage = (\n showing: number,\n total: number,\n isLazyLoad: boolean,\n): string =>\n isLazyLoad\n ? `Showing ${showing} of ${total} news articles`\n : `Showing ${showing} news articles`;\n\nclass InTheNewsFeedState {\n private stylesArray: string[] = [];\n private shadowRoot: ShadowRoot | null = null;\n private totalEntries: number = 0;\n private offset: number = 0;\n private pagination: PaginationState | null = null;\n\n constructor(initialStyles: string) {\n this.stylesArray.push(initialStyles);\n }\n\n addStyles(styles: string): void {\n this.stylesArray.push(styles);\n }\n\n setShadowRoot(shadow: ShadowRoot): void {\n this.shadowRoot = shadow;\n }\n\n async updateShadowStyles(): Promise<void> {\n if (!this.shadowRoot) return;\n await styleUtilities.setShadowStyles({\n shadowRoot: this.shadowRoot,\n styles: this.getStyles(),\n });\n }\n\n getStyles(): string {\n return this.stylesArray.join('\\n');\n }\n\n getShadowCallback(): (shadow: ShadowRoot) => void {\n return (shadow) => this.setShadowRoot(shadow);\n }\n\n getOffset(): number {\n return this.offset;\n }\n\n setOffset(value: number): void {\n this.offset = value;\n }\n\n incrementOffset(count: number): void {\n this.offset += count;\n }\n\n getTotalEntries(): number {\n return this.totalEntries;\n }\n\n setTotalEntries(total: number): void {\n this.totalEntries = total;\n }\n\n getPagination(): PaginationState | null {\n return this.pagination;\n }\n\n setPagination(pagination: PaginationState | null): void {\n this.pagination = pagination;\n }\n}\n\nconst createExpertOverlayCard = (\n expert: ExpertEntry,\n isThemeDark: boolean,\n): ElementModel =>\n expertsDisplayStrategy.mapEntryToCard(expert, {\n isOverlay: true,\n isThemeDark,\n });\n\nconst createNewsListContainer = (\n entries: InTheNewsEntry[],\n state: InTheNewsFeedState,\n isThemeDark: boolean,\n): HTMLElement => {\n const stackedLayout = stacked({ isThemeDark, showDividers: true, gap: '0' });\n stackedLayout.element.id = 'umd-expert-in-the-news-list';\n state.addStyles(stackedLayout.styles);\n\n entries.forEach((entry) => {\n const newsCard = inTheNewsDisplayStrategy.mapEntryToCard(entry, {\n isThemeDark,\n cardType: 'list',\n });\n stackedLayout.element.appendChild(newsCard.element);\n state.addStyles(newsCard.styles);\n });\n\n return stackedLayout.element;\n};\n\nconst appendNewsEntries = (\n container: HTMLElement,\n entries: InTheNewsEntry[],\n state: InTheNewsFeedState,\n isThemeDark: boolean,\n): void => {\n const listContainer = container.querySelector(\n '#umd-expert-in-the-news-list',\n ) as HTMLElement;\n\n if (!listContainer) return;\n\n entries.forEach((entry) => {\n const newsCard = inTheNewsDisplayStrategy.mapEntryToCard(entry, {\n isThemeDark,\n cardType: 'list',\n });\n listContainer.appendChild(newsCard.element);\n state.addStyles(newsCard.styles);\n });\n};\n\nconst renderLayout = async (\n container: HTMLElement,\n expert: ExpertEntry,\n newsEntries: InTheNewsEntry[],\n state: InTheNewsFeedState,\n props: InTheNewsProps,\n loadMore: () => Promise<void>,\n): Promise<void> => {\n const { isThemeDark = false, isLazyLoad = false } = props;\n\n const offsetLayout = gridOffset({\n columns: 2,\n isLayoutReversed: false,\n });\n\n const expertCard = createExpertOverlayCard(expert, isThemeDark);\n state.addStyles(expertCard.styles);\n\n state.addStyles(`\n .${card.overlay.imageClassRef} {\n height: inherit;\n }\n\n @media (${token.media.queries.tablet.min}) {\n .${card.overlay.imageClassRef} .card-overlay-image-container {\n min-height: 560px;\n }\n }\n\n .${card.overlay.imageClassRef} .umd-asset-image-wrapper-scaled {\n position: absolute;\n }\n `);\n\n const rightColumn = document.createElement('div');\n rightColumn.className = 'expert-in-the-news-right-column';\n\n const newsList = createNewsListContainer(newsEntries, state, isThemeDark);\n rightColumn.appendChild(newsList);\n\n offsetLayout.element.appendChild(expertCard.element);\n offsetLayout.element.appendChild(rightColumn);\n container.appendChild(offsetLayout.element);\n\n state.addStyles(offsetLayout.styles);\n state.setOffset(newsEntries.length);\n\n if (isLazyLoad && state.getTotalEntries() > state.getOffset()) {\n const pagination = new PaginationState({\n totalEntries: state.getTotalEntries(),\n offset: state.getOffset(),\n isLazyLoad: true,\n callback: loadMore,\n });\n\n const paginationElement = pagination.render(rightColumn);\n if (paginationElement) state.addStyles(paginationElement.styles);\n state.setPagination(pagination);\n }\n\n const announcer = new Announcer({\n message: createAnnouncerMessage(\n state.getOffset(),\n state.getTotalEntries(),\n isLazyLoad,\n ),\n });\n container.appendChild(announcer.getElement());\n\n await state.updateShadowStyles();\n};\n\nconst renderError = async (\n container: HTMLElement,\n message: string,\n state: InTheNewsFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const emptyState = new EmptyState({ message, isThemeDark });\n emptyState.render(container);\n state.addStyles(emptyState.styles);\n await state.updateShadowStyles();\n};\n\nconst renderLayoutWithNoNews = async (\n container: HTMLElement,\n expert: ExpertEntry,\n state: InTheNewsFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const offsetLayout = gridOffset({\n columns: 2,\n isLayoutReversed: false,\n });\n\n const expertCard = createExpertOverlayCard(expert, isThemeDark);\n state.addStyles(expertCard.styles);\n\n state.addStyles(`\n .${card.overlay.imageClassRef} {\n height: inherit;\n }\n\n @media (${token.media.queries.tablet.min}) {\n .${card.overlay.imageClassRef} .card-overlay-image-container {\n min-height: 560px;\n }\n }\n\n .${card.overlay.imageClassRef} .umd-asset-image-wrapper-scaled {\n position: absolute;\n }\n `);\n\n const rightColumn = document.createElement('div');\n rightColumn.className = 'expert-in-the-news-right-column';\n\n const emptyState = new EmptyState({\n message: 'No news articles found for this expert',\n isThemeDark,\n });\n emptyState.render(rightColumn);\n state.addStyles(emptyState.styles);\n\n offsetLayout.element.appendChild(expertCard.element);\n offsetLayout.element.appendChild(rightColumn);\n container.appendChild(offsetLayout.element);\n\n state.addStyles(offsetLayout.styles);\n await state.updateShadowStyles();\n};\n\nconst logError = (\n errorType: 'expert_not_found' | 'no_news' | 'graphql_error',\n expertId: string,\n error?: unknown,\n): void => {\n const messages = {\n expert_not_found: `No expert found with ID \"${expertId}\". Verify the expert ID is correct.`,\n no_news: `No news articles found for expert \"${expertId}\".`,\n graphql_error: `GraphQL error occurred for expert \"${expertId}\".`,\n };\n console.warn(`[Expert In The News Feed] ${messages[errorType]}`, error || '');\n};\n\nexport const expertsInTheNews = (props: InTheNewsProps): ElementModel => {\n const { token, expertId, isThemeDark = false, isLazyLoad = false } = props;\n const numberOfRowsToStart = normalizeRowCount(props.numberOfRowsToStart);\n\n const containerBuilder = new ElementBuilder('div').withClassName(\n 'expert-in-the-news-feed',\n );\n const container = containerBuilder.getElement();\n\n const loading = new LoadingState({ isThemeDark });\n const state = new InTheNewsFeedState(loading.styles);\n\n const loadMore = async (): Promise<void> => {\n const pagination = state.getPagination();\n if (pagination) pagination.remove();\n\n loading.show(container);\n\n const fetchProps = createNewsFetchProps(\n token,\n expertId,\n LOAD_MORE_ITEMS,\n state.getOffset(),\n );\n const variables = inTheNewsFetchStrategy.composeApiVariables(fetchProps);\n const entries = await inTheNewsFetchStrategy.fetchEntries(variables);\n\n loading.hide();\n\n if (!entries || entries.length === 0) return;\n\n appendNewsEntries(container, entries, state, isThemeDark);\n state.incrementOffset(entries.length);\n\n if (pagination) {\n pagination.updateState(state.getOffset(), state.getTotalEntries());\n if (pagination.styles) state.addStyles(pagination.styles);\n await state.updateShadowStyles();\n }\n\n const existingAnnouncer = container.querySelector(\n '[role=\"status\"]',\n ) as HTMLElement;\n if (existingAnnouncer) {\n existingAnnouncer.textContent = createAnnouncerMessage(\n state.getOffset(),\n state.getTotalEntries(),\n isLazyLoad,\n );\n }\n\n eventUtilities.dispatch(\n container,\n eventUtilities.eventNames.FEED_LOADED_MORE,\n { items: entries, count: entries.length, total: state.getTotalEntries() },\n );\n };\n\n const initialize = async (): Promise<void> => {\n if (!expertId) {\n await renderError(container, 'Expert ID is required', state, isThemeDark);\n return;\n }\n\n loading.show(container);\n\n try {\n const expertFetchProps = createExpertFetchProps(token, expertId);\n const expertVariables =\n expertsFetchStrategy.composeApiVariables(expertFetchProps);\n\n const newsFetchProps = createNewsFetchProps(\n token,\n expertId,\n numberOfRowsToStart,\n 0,\n );\n const newsVariables =\n inTheNewsFetchStrategy.composeApiVariables(newsFetchProps);\n\n const [expertEntries, newsCount, newsEntries] = await Promise.all([\n expertsFetchStrategy.fetchEntries(expertVariables),\n inTheNewsFetchStrategy.fetchCount(newsVariables),\n inTheNewsFetchStrategy.fetchEntries(newsVariables),\n ]);\n\n loading.hide();\n\n if (!expertEntries || expertEntries.length === 0) {\n logError('expert_not_found', expertId);\n await renderError(container, 'Expert not found', state, isThemeDark);\n return;\n }\n\n if (!newsEntries || newsEntries.length === 0) {\n logError('no_news', expertId);\n await renderLayoutWithNoNews(\n container,\n expertEntries[0],\n state,\n isThemeDark,\n );\n return;\n }\n\n state.setTotalEntries(newsCount || newsEntries.length);\n\n eventUtilities.dispatch(\n container,\n eventUtilities.eventNames.FEED_LOADED,\n {\n expert: expertEntries[0],\n newsItems: newsEntries,\n newsCount: newsEntries.length,\n total: state.getTotalEntries(),\n },\n );\n\n await renderLayout(\n container,\n expertEntries[0],\n newsEntries,\n state,\n props,\n loadMore,\n );\n } catch (error) {\n loading.hide();\n logError('graphql_error', expertId, error);\n await renderError(\n container,\n 'Failed to load content',\n state,\n isThemeDark,\n );\n }\n };\n\n initialize();\n\n return {\n element: containerBuilder.build().element,\n get styles() {\n return state.getStyles();\n },\n events: {\n callback: state.getShadowCallback(),\n },\n };\n};\n"],"names":["token","styleUtilities.setShadowStyles","eventUtilities.dispatch","eventUtilities.eventNames"],"mappings":"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"}
1
+ {"version":3,"file":"in-the-news.js","sources":["../../../source/feeds/experts/in-the-news.ts"],"sourcesContent":["import { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport * as token from '@universityofmaryland/web-token-library';\nimport { card } from '@universityofmaryland/web-elements-library/composite';\nimport { gridOffset, stacked } from '@universityofmaryland/web-elements-library/layout';\nimport {\n LoadingState,\n PaginationState,\n EmptyState,\n Announcer,\n} from '../../states';\nimport { expertsFetchStrategy } from '../../strategies/fetch/experts';\nimport { inTheNewsFetchStrategy } from '../../strategies/fetch/inTheNews';\nimport { expertsDisplayStrategy } from '../../strategies/display/experts';\nimport { inTheNewsDisplayStrategy } from '../../strategies/display/inTheNews';\nimport {\n events as eventUtilities,\n styles as styleUtilities,\n} from '../../helpers';\nimport { type InTheNewsProps } from './_types';\nimport { type ElementModel } from '../../_types';\nimport { type ExpertEntry, type InTheNewsEntry } from 'types/data';\n\nconst DEFAULT_ROWS_TO_START = 5;\nconst MIN_ROWS_TO_START = 2;\nconst MAX_ROWS_TO_START = 10;\nconst LOAD_MORE_ITEMS = 5;\n\nconst normalizeRowCount = (rows?: number): number => {\n if (rows === undefined) return DEFAULT_ROWS_TO_START;\n return Math.max(MIN_ROWS_TO_START, Math.min(MAX_ROWS_TO_START, rows));\n};\n\nconst createExpertFetchProps = (token: string, expertId: string) => ({\n token,\n limit: 1,\n offset: 0,\n ids: [expertId],\n});\n\nconst createNewsFetchProps = (\n token: string,\n expertId: string,\n numberOfRowsToStart: number,\n offset: number,\n) => ({\n token,\n expertId,\n numberOfRowsToStart,\n getOffset: () => offset,\n});\n\nconst createAnnouncerMessage = (\n showing: number,\n total: number,\n isLazyLoad: boolean,\n): string =>\n isLazyLoad\n ? `Showing ${showing} of ${total} news articles`\n : `Showing ${showing} news articles`;\n\nclass InTheNewsFeedState {\n private stylesArray: string[] = [];\n private shadowRoot: ShadowRoot | null = null;\n private totalEntries: number = 0;\n private offset: number = 0;\n private pagination: PaginationState | null = null;\n\n constructor(initialStyles: string) {\n this.stylesArray.push(initialStyles);\n }\n\n addStyles(styles: string): void {\n this.stylesArray.push(styles);\n }\n\n setShadowRoot(shadow: ShadowRoot): void {\n this.shadowRoot = shadow;\n }\n\n async updateShadowStyles(): Promise<void> {\n if (!this.shadowRoot) return;\n await styleUtilities.setShadowStyles({\n shadowRoot: this.shadowRoot,\n styles: this.getStyles(),\n });\n }\n\n getStyles(): string {\n return this.stylesArray.join('\\n');\n }\n\n getShadowCallback(): (shadow: ShadowRoot) => void {\n return (shadow) => this.setShadowRoot(shadow);\n }\n\n getOffset(): number {\n return this.offset;\n }\n\n setOffset(value: number): void {\n this.offset = value;\n }\n\n incrementOffset(count: number): void {\n this.offset += count;\n }\n\n getTotalEntries(): number {\n return this.totalEntries;\n }\n\n setTotalEntries(total: number): void {\n this.totalEntries = total;\n }\n\n getPagination(): PaginationState | null {\n return this.pagination;\n }\n\n setPagination(pagination: PaginationState | null): void {\n this.pagination = pagination;\n }\n}\n\nconst createExpertOverlayCard = (\n expert: ExpertEntry,\n isThemeDark: boolean,\n): ElementModel =>\n expertsDisplayStrategy.mapEntryToCard(expert, {\n isOverlay: true,\n isThemeDark,\n });\n\nconst createNewsListContainer = (\n entries: InTheNewsEntry[],\n state: InTheNewsFeedState,\n isThemeDark: boolean,\n): HTMLElement => {\n const stackedLayout = stacked({ isThemeDark, showDividers: true, gap: '0' });\n stackedLayout.element.id = 'umd-expert-in-the-news-list';\n state.addStyles(stackedLayout.styles);\n\n entries.forEach((entry) => {\n const newsCard = inTheNewsDisplayStrategy.mapEntryToCard(entry, {\n isThemeDark,\n cardType: 'list',\n });\n stackedLayout.element.appendChild(newsCard.element);\n state.addStyles(newsCard.styles);\n });\n\n return stackedLayout.element;\n};\n\nconst appendNewsEntries = (\n container: HTMLElement,\n entries: InTheNewsEntry[],\n state: InTheNewsFeedState,\n isThemeDark: boolean,\n): void => {\n const listContainer = container.querySelector(\n '#umd-expert-in-the-news-list',\n ) as HTMLElement;\n\n if (!listContainer) return;\n\n entries.forEach((entry) => {\n const newsCard = inTheNewsDisplayStrategy.mapEntryToCard(entry, {\n isThemeDark,\n cardType: 'list',\n });\n listContainer.appendChild(newsCard.element);\n state.addStyles(newsCard.styles);\n });\n};\n\nconst renderLayout = async (\n container: HTMLElement,\n expert: ExpertEntry,\n newsEntries: InTheNewsEntry[],\n state: InTheNewsFeedState,\n props: InTheNewsProps,\n loadMore: () => Promise<void>,\n): Promise<void> => {\n const { isThemeDark = false, isLazyLoad = false } = props;\n\n const offsetLayout = gridOffset({\n columns: 2,\n isLayoutReversed: false,\n });\n\n const expertCard = createExpertOverlayCard(expert, isThemeDark);\n state.addStyles(expertCard.styles);\n\n state.addStyles(`\n .${card.overlay.imageClassRef} {\n height: inherit;\n }\n\n @media (${token.media.queries.tablet.min}) {\n .${card.overlay.imageClassRef} .card-overlay-image-container {\n min-height: 560px;\n }\n }\n\n .${card.overlay.imageClassRef} .umd-asset-image-wrapper-scaled {\n position: absolute;\n }\n `);\n\n const rightColumn = document.createElement('div');\n rightColumn.className = 'expert-in-the-news-right-column';\n\n const newsList = createNewsListContainer(newsEntries, state, isThemeDark);\n rightColumn.appendChild(newsList);\n\n offsetLayout.element.appendChild(expertCard.element);\n offsetLayout.element.appendChild(rightColumn);\n container.appendChild(offsetLayout.element);\n\n state.addStyles(offsetLayout.styles);\n state.setOffset(newsEntries.length);\n\n if (isLazyLoad && state.getTotalEntries() > state.getOffset()) {\n const pagination = new PaginationState({\n totalEntries: state.getTotalEntries(),\n offset: state.getOffset(),\n isLazyLoad: true,\n callback: loadMore,\n isThemeDark,\n });\n\n const paginationElement = pagination.render(rightColumn);\n if (paginationElement) state.addStyles(paginationElement.styles);\n state.setPagination(pagination);\n }\n\n const announcer = new Announcer({\n message: createAnnouncerMessage(\n state.getOffset(),\n state.getTotalEntries(),\n isLazyLoad,\n ),\n });\n container.appendChild(announcer.getElement());\n\n await state.updateShadowStyles();\n};\n\nconst renderError = async (\n container: HTMLElement,\n message: string,\n state: InTheNewsFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const emptyState = new EmptyState({ message, isThemeDark });\n emptyState.render(container);\n state.addStyles(emptyState.styles);\n await state.updateShadowStyles();\n};\n\nconst renderLayoutWithNoNews = async (\n container: HTMLElement,\n expert: ExpertEntry,\n state: InTheNewsFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const offsetLayout = gridOffset({\n columns: 2,\n isLayoutReversed: false,\n });\n\n const expertCard = createExpertOverlayCard(expert, isThemeDark);\n state.addStyles(expertCard.styles);\n\n state.addStyles(`\n .${card.overlay.imageClassRef} {\n height: inherit;\n }\n\n @media (${token.media.queries.tablet.min}) {\n .${card.overlay.imageClassRef} .card-overlay-image-container {\n min-height: 560px;\n }\n }\n\n .${card.overlay.imageClassRef} .umd-asset-image-wrapper-scaled {\n position: absolute;\n }\n `);\n\n const rightColumn = document.createElement('div');\n rightColumn.className = 'expert-in-the-news-right-column';\n\n const emptyState = new EmptyState({\n message: 'No news articles found for this expert',\n isThemeDark,\n });\n emptyState.render(rightColumn);\n state.addStyles(emptyState.styles);\n\n offsetLayout.element.appendChild(expertCard.element);\n offsetLayout.element.appendChild(rightColumn);\n container.appendChild(offsetLayout.element);\n\n state.addStyles(offsetLayout.styles);\n await state.updateShadowStyles();\n};\n\nconst logError = (\n errorType: 'expert_not_found' | 'no_news' | 'graphql_error',\n expertId: string,\n error?: unknown,\n): void => {\n const messages = {\n expert_not_found: `No expert found with ID \"${expertId}\". Verify the expert ID is correct.`,\n no_news: `No news articles found for expert \"${expertId}\".`,\n graphql_error: `GraphQL error occurred for expert \"${expertId}\".`,\n };\n console.warn(`[Expert In The News Feed] ${messages[errorType]}`, error || '');\n};\n\nexport const expertsInTheNews = (props: InTheNewsProps): ElementModel => {\n const { token, expertId, isThemeDark = false, isLazyLoad = false } = props;\n const numberOfRowsToStart = normalizeRowCount(props.numberOfRowsToStart);\n\n const containerBuilder = new ElementBuilder('div').withClassName(\n 'expert-in-the-news-feed',\n );\n const container = containerBuilder.getElement();\n\n const loading = new LoadingState({ isThemeDark });\n const state = new InTheNewsFeedState(loading.styles);\n\n const loadMore = async (): Promise<void> => {\n const pagination = state.getPagination();\n if (pagination) pagination.remove();\n\n loading.show(container);\n\n const fetchProps = createNewsFetchProps(\n token,\n expertId,\n LOAD_MORE_ITEMS,\n state.getOffset(),\n );\n const variables = inTheNewsFetchStrategy.composeApiVariables(fetchProps);\n const entries = await inTheNewsFetchStrategy.fetchEntries(variables);\n\n loading.hide();\n\n if (!entries || entries.length === 0) return;\n\n appendNewsEntries(container, entries, state, isThemeDark);\n state.incrementOffset(entries.length);\n\n if (pagination) {\n pagination.updateState(state.getOffset(), state.getTotalEntries());\n if (pagination.styles) state.addStyles(pagination.styles);\n await state.updateShadowStyles();\n }\n\n const existingAnnouncer = container.querySelector(\n '[role=\"status\"]',\n ) as HTMLElement;\n if (existingAnnouncer) {\n existingAnnouncer.textContent = createAnnouncerMessage(\n state.getOffset(),\n state.getTotalEntries(),\n isLazyLoad,\n );\n }\n\n eventUtilities.dispatch(\n container,\n eventUtilities.eventNames.FEED_LOADED_MORE,\n { items: entries, count: entries.length, total: state.getTotalEntries() },\n );\n };\n\n const initialize = async (): Promise<void> => {\n if (!expertId) {\n await renderError(container, 'Expert ID is required', state, isThemeDark);\n return;\n }\n\n loading.show(container);\n\n try {\n const expertFetchProps = createExpertFetchProps(token, expertId);\n const expertVariables =\n expertsFetchStrategy.composeApiVariables(expertFetchProps);\n\n const newsFetchProps = createNewsFetchProps(\n token,\n expertId,\n numberOfRowsToStart,\n 0,\n );\n const newsVariables =\n inTheNewsFetchStrategy.composeApiVariables(newsFetchProps);\n\n const [expertEntries, newsCount, newsEntries] = await Promise.all([\n expertsFetchStrategy.fetchEntries(expertVariables),\n inTheNewsFetchStrategy.fetchCount(newsVariables),\n inTheNewsFetchStrategy.fetchEntries(newsVariables),\n ]);\n\n loading.hide();\n\n if (!expertEntries || expertEntries.length === 0) {\n logError('expert_not_found', expertId);\n await renderError(container, 'Expert not found', state, isThemeDark);\n return;\n }\n\n if (!newsEntries || newsEntries.length === 0) {\n logError('no_news', expertId);\n await renderLayoutWithNoNews(\n container,\n expertEntries[0],\n state,\n isThemeDark,\n );\n return;\n }\n\n state.setTotalEntries(newsCount || newsEntries.length);\n\n eventUtilities.dispatch(\n container,\n eventUtilities.eventNames.FEED_LOADED,\n {\n expert: expertEntries[0],\n newsItems: newsEntries,\n newsCount: newsEntries.length,\n total: state.getTotalEntries(),\n },\n );\n\n await renderLayout(\n container,\n expertEntries[0],\n newsEntries,\n state,\n props,\n loadMore,\n );\n } catch (error) {\n loading.hide();\n logError('graphql_error', expertId, error);\n await renderError(\n container,\n 'Failed to load content',\n state,\n isThemeDark,\n );\n }\n };\n\n initialize();\n\n return {\n element: containerBuilder.build().element,\n get styles() {\n return state.getStyles();\n },\n events: {\n callback: state.getShadowCallback(),\n },\n };\n};\n"],"names":["token","styleUtilities.setShadowStyles","eventUtilities.dispatch","eventUtilities.eventNames"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"featured.d.ts","sourceRoot":"","sources":["../../../source/feeds/news/featured.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAuejD,eAAO,MAAM,YAAY,GAAI,OAAO,aAAa,KAAG,YAsJnD,CAAC"}
1
+ {"version":3,"file":"featured.d.ts","sourceRoot":"","sources":["../../../source/feeds/news/featured.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAwejD,eAAO,MAAM,YAAY,GAAI,OAAO,aAAa,KAAG,YAsJnD,CAAC"}
@@ -189,11 +189,11 @@ const createOverlayCard = (entry, state, isThemeDark) => {
189
189
  `);
190
190
  return overlayCard;
191
191
  };
192
- const createBlockCards = (entries, state, options) => {
192
+ const createBlockCards = (entries, state, options2) => {
193
193
  return entries.map((entry) => {
194
194
  const blockCard = newsDisplayStrategy.mapEntryToCard(entry, {
195
- isThemeDark: options.isThemeDark,
196
- isTransparent: options.isTransparent,
195
+ isThemeDark: options2.isThemeDark,
196
+ isTransparent: options2.isTransparent,
197
197
  isAligned: true,
198
198
  imageConfig: () => createImageConfig(entry)
199
199
  });
@@ -242,7 +242,8 @@ const renderFeaturedLayout = async (container, entries, state, props, loadMore)
242
242
  totalEntries: state.getTotalEntries(),
243
243
  offset: state.getOffset(),
244
244
  isLazyLoad: true,
245
- callback: loadMore
245
+ callback: loadMore,
246
+ isThemeDark: options.isThemeDark
246
247
  });
247
248
  const paginationElement = pagination.render(container);
248
249
  if (paginationElement) state.addStyles(paginationElement.styles);
@@ -257,7 +258,7 @@ const renderFeaturedLayout = async (container, entries, state, props, loadMore)
257
258
  container.appendChild(announcer.getElement());
258
259
  await state.updateShadowStyles();
259
260
  };
260
- const renderStandardGrid = async (container, entries, state, options) => {
261
+ const renderStandardGrid = async (container, entries, state, options2) => {
261
262
  let gridContainer = container.querySelector(
262
263
  "#umd-featured-news-grid-container"
263
264
  );
@@ -268,7 +269,7 @@ const renderStandardGrid = async (container, entries, state, options) => {
268
269
  state.addStyles(gridLayout.styles);
269
270
  gridContainer = gridLayout.element;
270
271
  }
271
- const blockCards = createBlockCards(entries, state, options);
272
+ const blockCards = createBlockCards(entries, state, options2);
272
273
  blockCards.forEach((card2) => {
273
274
  gridContainer.appendChild(card2.element);
274
275
  });
@@ -1 +1 @@
1
- {"version":3,"file":"featured.js","sources":["../../../source/feeds/news/featured.ts"],"sourcesContent":["/**\n * News Featured Feed (Refactored with Element Builder)\n *\n * Displays news articles with a featured layout:\n * - First article: Large overlay card with sticky positioning\n * - Next 2 articles: Block cards in grid layout\n * - Additional articles: Lazy-loaded block cards\n *\n * Uses Element Builder pattern for clean, declarative construction.\n *\n * @module feeds/news/featured\n */\n\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport * as token from '@universityofmaryland/web-token-library';\nimport { card } from '@universityofmaryland/web-elements-library/composite';\nimport {\n gridGap,\n gridOffset,\n} from '@universityofmaryland/web-elements-library/layout';\nimport {\n LoadingState,\n PaginationState,\n EmptyState,\n Announcer,\n} from '../../states';\nimport { newsFetchStrategy } from '../../strategies/fetch/news';\nimport { newsDisplayStrategy } from '../../strategies/display/news';\nimport {\n events as eventUtilities,\n styles as styleUtilities,\n} from '../../helpers';\nimport { type FeaturedProps } from './_types';\nimport { type ElementModel } from '../../_types';\nimport { type NewsEntry } from 'types/data';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\n/** Featured layout displays 3 items initially: 1 overlay + 2 block cards */\nconst INITIAL_ITEMS = 3;\n\n/** Lazy loading adds 2 items at a time to fill a row in the 2-column grid */\nconst LOAD_MORE_ITEMS = 2;\n\n// ============================================================================\n// PURE HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Create base props for fetch strategy\n *\n * @param props - Feed props\n * @param offset - Current offset\n * @returns Base props object for strategy's composeApiVariables\n */\nconst createFetchProps = (\n props: Pick<FeaturedProps, 'token' | 'categories' | 'isUnion'>,\n offset: number,\n) => ({\n token: props.token,\n categories: props.categories,\n isUnion: props.isUnion,\n numberOfRowsToStart: offset === 0 ? INITIAL_ITEMS : LOAD_MORE_ITEMS,\n numberOfColumnsToShow: 1,\n getOffset: () => offset,\n});\n\n/**\n * Create image configuration for news entry\n *\n * @param entry - News entry\n * @returns Image config object\n */\nconst createImageConfig = (entry: NewsEntry) => {\n const imageUrl = entry.image?.[0]?.url;\n const altText = entry.image?.[0]?.altText;\n\n if (!imageUrl || !altText) return null;\n\n return {\n imageUrl: imageUrl,\n altText: altText,\n linkUrl: entry.url,\n linkLabel: 'Maryland Today Article with image',\n };\n};\n\n/**\n * Create announcer message\n *\n * @param offset - Current offset\n * @param total - Total entries\n * @param isLazyLoad - Lazy load enabled\n * @returns Announcer message\n */\nconst createAnnouncerMessage = (\n offset: number,\n total: number,\n isLazyLoad: boolean,\n): string => {\n return isLazyLoad\n ? `Showing ${offset} of ${total} articles`\n : `Showing ${offset} articles`;\n};\n\n// ============================================================================\n// STATE MANAGER CLASS\n// ============================================================================\n\n/**\n * Manages featured feed state and shadow DOM synchronization\n *\n * Encapsulates all mutable state including pagination, offset,\n * and shadow DOM management.\n */\nclass FeaturedFeedState {\n private stylesArray: string[] = [];\n private shadowRoot: ShadowRoot | null = null;\n private totalEntries: number = 0;\n private offset: number = 0;\n private hasRenderedOffset: boolean = false;\n private pagination: PaginationState | null = null;\n\n /**\n * Initialize state with initial styles\n *\n * @param initialStyles - Initial CSS styles\n */\n constructor(initialStyles: string) {\n this.stylesArray.push(initialStyles);\n }\n\n /**\n * Add styles to the accumulated styles\n *\n * @param styles - CSS styles to add\n */\n addStyles(styles: string): void {\n this.stylesArray.push(styles);\n }\n\n /**\n * Set shadow root reference for style updates\n *\n * @param shadow - Shadow root element\n */\n setShadowRoot(shadow: ShadowRoot): void {\n this.shadowRoot = shadow;\n }\n\n /**\n * Update shadow DOM styles\n *\n * @returns Promise that resolves when styles are updated\n */\n async updateShadowStyles(): Promise<void> {\n if (!this.shadowRoot) return;\n await styleUtilities.setShadowStyles({\n shadowRoot: this.shadowRoot,\n styles: this.getStyles(),\n });\n }\n\n /**\n * Get accumulated styles as single string\n *\n * @returns Combined CSS styles\n */\n getStyles(): string {\n return this.stylesArray.join('\\n');\n }\n\n /**\n * Get shadow root callback for events\n *\n * @returns Callback function for shadow root\n */\n getShadowCallback(): (shadow: ShadowRoot) => void {\n return (shadow) => this.setShadowRoot(shadow);\n }\n\n /**\n * Get current offset\n *\n * @returns Current offset\n */\n getOffset(): number {\n return this.offset;\n }\n\n /**\n * Set offset to specific value\n *\n * @param value - New offset value\n */\n setOffset(value: number): void {\n this.offset = value;\n }\n\n /**\n * Increment offset by count\n *\n * @param count - Number to increment by\n */\n incrementOffset(count: number): void {\n this.offset += count;\n }\n\n /**\n * Get total entries\n *\n * @returns Total entries\n */\n getTotalEntries(): number {\n return this.totalEntries;\n }\n\n /**\n * Set total entries\n *\n * @param total - Total entries\n */\n setTotalEntries(total: number): void {\n this.totalEntries = total;\n }\n\n /**\n * Check if offset layout has been rendered\n *\n * @returns True if offset layout rendered\n */\n hasOffset(): boolean {\n return this.hasRenderedOffset;\n }\n\n /**\n * Mark offset layout as rendered\n */\n markOffsetRendered(): void {\n this.hasRenderedOffset = true;\n }\n\n /**\n * Get pagination state\n *\n * @returns Pagination state or null\n */\n getPagination(): PaginationState | null {\n return this.pagination;\n }\n\n /**\n * Set pagination state\n *\n * @param pagination - Pagination state\n */\n setPagination(pagination: PaginationState | null): void {\n this.pagination = pagination;\n }\n}\n\n// ============================================================================\n// RENDERING FUNCTIONS\n// ============================================================================\n\n/**\n * Create overlay card for featured entry\n *\n * @param entry - News entry\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @returns Overlay card element model\n */\nconst createOverlayCard = (\n entry: NewsEntry,\n state: FeaturedFeedState,\n isThemeDark: boolean,\n): ElementModel => {\n const overlayCard = newsDisplayStrategy.mapEntryToCard(entry, {\n isOverlay: true,\n isThemeDark,\n imageConfig: () => createImageConfig(entry),\n });\n\n // Add custom overlay styles\n state.addStyles(`\n ${overlayCard.styles}\n\n .${card.overlay.imageClassRef} {\n height: inherit;\n }\n\n @media (${token.media.queries.tablet.min}) {\n .${card.overlay.imageClassRef} .card-overlay-image-container {\n min-height: 560px;\n }\n }\n\n .${card.overlay.imageClassRef} .umd-asset-image-wrapper-scaled {\n position: absolute;\n }\n `);\n\n return overlayCard;\n};\n\n/**\n * Create block cards for entries\n *\n * @param entries - News entries\n * @param state - State manager\n * @param options - Card options\n * @returns Array of block card element models\n */\nconst createBlockCards = (\n entries: NewsEntry[],\n state: FeaturedFeedState,\n options: { isThemeDark: boolean; isTransparent: boolean },\n): ElementModel[] => {\n return entries.map((entry) => {\n const blockCard = newsDisplayStrategy.mapEntryToCard(entry, {\n isThemeDark: options.isThemeDark,\n isTransparent: options.isTransparent,\n isAligned: true,\n imageConfig: () => createImageConfig(entry),\n });\n\n state.addStyles(blockCard.styles);\n return blockCard;\n });\n};\n\n/**\n * Render featured layout (initial load only)\n *\n * @param container - Container element\n * @param entries - News entries\n * @param state - State manager\n * @param props - Feed props\n * @param loadMore - Load more callback\n * @returns Promise that resolves when rendering is complete\n */\nconst renderFeaturedLayout = async (\n container: HTMLElement,\n entries: NewsEntry[],\n state: FeaturedFeedState,\n props: FeaturedProps,\n loadMore: () => Promise<void>,\n): Promise<void> => {\n const {\n isThemeDark = false,\n isTransparent = false,\n isLayoutReversed = false,\n overwriteStickyPosition,\n isLazyLoad = false,\n } = props;\n\n // Fall back to standard grid if not enough entries or already rendered\n if (entries.length < 2 || state.hasOffset()) {\n return renderStandardGrid(container, entries, state, {\n isThemeDark,\n isTransparent,\n });\n }\n\n state.markOffsetRendered();\n\n // Create offset layout\n const offsetLayout = gridOffset({\n columns: 2,\n isLayoutReversed,\n stickyTopPosition: overwriteStickyPosition,\n });\n\n // Create grid for remaining items\n const gridLayout = gridGap({ columns: 2 });\n gridLayout.element.setAttribute('id', 'umd-featured-news-grid-container');\n\n // First item: overlay card\n const overlayCard = createOverlayCard(entries[0], state, isThemeDark);\n\n // Next 2 items: block cards\n const blockCards = createBlockCards(entries.slice(1, 3), state, {\n isThemeDark,\n isTransparent,\n });\n\n // Append block cards to grid\n blockCards.forEach((card) => {\n gridLayout.element.appendChild(card.element);\n });\n\n // Assemble offset layout\n offsetLayout.element.appendChild(overlayCard.element);\n offsetLayout.element.appendChild(gridLayout.element);\n container.appendChild(offsetLayout.element);\n\n state.addStyles(offsetLayout.styles);\n state.addStyles(gridLayout.styles);\n state.setOffset(3); // We've shown 3 items\n\n // Add pagination if needed\n if (isLazyLoad && state.getTotalEntries() > state.getOffset()) {\n const pagination = new PaginationState({\n totalEntries: state.getTotalEntries(),\n offset: state.getOffset(),\n isLazyLoad: true,\n callback: loadMore,\n });\n\n const paginationElement = pagination.render(container);\n if (paginationElement) state.addStyles(paginationElement.styles);\n state.setPagination(pagination);\n }\n\n // Announcer\n const message = createAnnouncerMessage(\n INITIAL_ITEMS,\n state.getTotalEntries(),\n isLazyLoad,\n );\n const announcer = new Announcer({ message });\n container.appendChild(announcer.getElement());\n\n await state.updateShadowStyles();\n};\n\n/**\n * Render standard grid (for lazy-loaded items or fallback)\n *\n * @param container - Container element\n * @param entries - News entries\n * @param state - State manager\n * @param options - Rendering options\n * @returns Promise that resolves when rendering is complete\n */\nconst renderStandardGrid = async (\n container: HTMLElement,\n entries: NewsEntry[],\n state: FeaturedFeedState,\n options: { isThemeDark: boolean; isTransparent: boolean },\n): Promise<void> => {\n let gridContainer = container.querySelector(\n '#umd-featured-news-grid-container',\n ) as HTMLElement;\n\n // Create grid if it doesn't exist\n if (!gridContainer) {\n const gridLayout = gridGap({ columns: 2 });\n gridLayout.element.setAttribute('id', 'umd-featured-news-grid-container');\n container.appendChild(gridLayout.element);\n state.addStyles(gridLayout.styles);\n gridContainer = gridLayout.element;\n }\n\n // Create and append block cards\n const blockCards = createBlockCards(entries, state, options);\n blockCards.forEach((card) => {\n gridContainer.appendChild(card.element);\n });\n\n state.incrementOffset(entries.length);\n\n await state.updateShadowStyles();\n};\n\n/**\n * Render error state\n *\n * @param container - Container element\n * @param message - Error message\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @returns Promise that resolves when rendering is complete\n */\nconst renderError = async (\n container: HTMLElement,\n message: string,\n state: FeaturedFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const emptyState = new EmptyState({ message, isThemeDark });\n emptyState.render(container);\n state.addStyles(emptyState.styles);\n await state.updateShadowStyles();\n};\n\n// ============================================================================\n// MAIN EXPORT\n// ============================================================================\n\n/**\n * Create a featured news feed\n *\n * Displays news with featured layout: overlay card + grid.\n * Uses Element Builder pattern for clean construction.\n *\n * @param props - Feed configuration\n * @returns ElementModel with feed element, styles, and events\n *\n * @example\n * ```typescript\n * const feed = newsFeatured({\n * token: 'my-token',\n * isLazyLoad: true,\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With custom sticky position\n * const feed = newsFeatured({\n * token: 'my-token',\n * overwriteStickyPosition: 100,\n * isLayoutReversed: true,\n * });\n * ```\n */\nexport const newsFeatured = (props: FeaturedProps): ElementModel => {\n const {\n token,\n categories,\n isUnion,\n isThemeDark = false,\n isLazyLoad = false,\n isTransparent = false,\n } = props;\n\n // Create container using ElementBuilder\n const containerBuilder = new ElementBuilder('div').withClassName(\n 'featured-news-feed',\n );\n\n // Get element for manipulation (non-destructive)\n const container = containerBuilder.getElement();\n\n // Initialize state management\n const loading = new LoadingState({ isThemeDark });\n const state = new FeaturedFeedState(loading.styles);\n\n /**\n * Load more articles (for lazy loading)\n */\n const loadMore = async (): Promise<void> => {\n // Remove pagination button\n const pagination = state.getPagination();\n if (pagination) {\n pagination.remove();\n }\n\n // Show loading indicator\n loading.show(container);\n\n // Load more items\n const fetchProps = createFetchProps(\n { token, categories, isUnion },\n state.getOffset(),\n );\n const variables = newsFetchStrategy.composeApiVariables(fetchProps);\n\n const entries = await newsFetchStrategy.fetchEntries(variables);\n\n // Hide loading indicator\n loading.hide();\n\n if (!entries || entries.length === 0) return;\n\n await renderStandardGrid(container, entries, state, {\n isThemeDark,\n isTransparent,\n });\n\n // Update pagination state\n if (pagination) {\n pagination.updateState(state.getOffset(), state.getTotalEntries());\n if (pagination.styles) state.addStyles(pagination.styles);\n await state.updateShadowStyles();\n }\n\n // Update announcer\n const existingAnnouncer = container.querySelector(\n '[role=\"status\"]',\n ) as HTMLElement;\n if (existingAnnouncer) {\n existingAnnouncer.textContent = createAnnouncerMessage(\n state.getOffset(),\n state.getTotalEntries(),\n isLazyLoad,\n );\n }\n\n // Dispatch update event\n eventUtilities.dispatch(\n container,\n eventUtilities.eventNames.FEED_LOADED_MORE,\n {\n items: entries,\n count: entries.length,\n total: state.getTotalEntries(),\n },\n );\n };\n\n /**\n * Initialize feed\n */\n const initialize = async (): Promise<void> => {\n loading.show(container);\n\n // Fetch initial items\n const fetchProps = createFetchProps({ token, categories, isUnion }, 0);\n const variables = newsFetchStrategy.composeApiVariables(fetchProps);\n\n const [count, entries] = await Promise.all([\n newsFetchStrategy.fetchCount(variables),\n newsFetchStrategy.fetchEntries(variables),\n ]);\n\n loading.hide();\n\n // Handle no results\n if (!entries || entries.length === 0) {\n await renderError(\n container,\n 'No news articles found',\n state,\n isThemeDark,\n );\n return;\n }\n\n state.setTotalEntries(count || entries.length);\n\n // Dispatch loaded event\n eventUtilities.dispatch(container, eventUtilities.eventNames.FEED_LOADED, {\n items: entries,\n count: entries.length,\n total: state.getTotalEntries(),\n });\n\n // Render featured layout\n await renderFeaturedLayout(container, entries, state, props, loadMore);\n };\n\n // Start initialization\n initialize();\n\n // Build and return element model\n const model = containerBuilder.build();\n\n // Custom event: allow external control of sticky position\n const setPosition = (position: number) => {\n const overlayElement = container.querySelector(\n `.${card.overlay.imageClassRef}`,\n ) as HTMLElement;\n if (overlayElement) overlayElement.style.top = `${position}px`;\n };\n\n return {\n element: model.element,\n get styles() {\n return state.getStyles();\n },\n events: {\n callback: state.getShadowCallback(),\n setPosition, // Custom event for sticky position control\n },\n };\n};\n"],"names":["styleUtilities.setShadowStyles","card","token","eventUtilities.dispatch","eventUtilities.eventNames"],"mappings":"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"}
1
+ {"version":3,"file":"featured.js","sources":["../../../source/feeds/news/featured.ts"],"sourcesContent":["/**\n * News Featured Feed (Refactored with Element Builder)\n *\n * Displays news articles with a featured layout:\n * - First article: Large overlay card with sticky positioning\n * - Next 2 articles: Block cards in grid layout\n * - Additional articles: Lazy-loaded block cards\n *\n * Uses Element Builder pattern for clean, declarative construction.\n *\n * @module feeds/news/featured\n */\n\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport * as token from '@universityofmaryland/web-token-library';\nimport { card } from '@universityofmaryland/web-elements-library/composite';\nimport {\n gridGap,\n gridOffset,\n} from '@universityofmaryland/web-elements-library/layout';\nimport {\n LoadingState,\n PaginationState,\n EmptyState,\n Announcer,\n} from '../../states';\nimport { newsFetchStrategy } from '../../strategies/fetch/news';\nimport { newsDisplayStrategy } from '../../strategies/display/news';\nimport {\n events as eventUtilities,\n styles as styleUtilities,\n} from '../../helpers';\nimport { type FeaturedProps } from './_types';\nimport { type ElementModel } from '../../_types';\nimport { type NewsEntry } from 'types/data';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\n/** Featured layout displays 3 items initially: 1 overlay + 2 block cards */\nconst INITIAL_ITEMS = 3;\n\n/** Lazy loading adds 2 items at a time to fill a row in the 2-column grid */\nconst LOAD_MORE_ITEMS = 2;\n\n// ============================================================================\n// PURE HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Create base props for fetch strategy\n *\n * @param props - Feed props\n * @param offset - Current offset\n * @returns Base props object for strategy's composeApiVariables\n */\nconst createFetchProps = (\n props: Pick<FeaturedProps, 'token' | 'categories' | 'isUnion'>,\n offset: number,\n) => ({\n token: props.token,\n categories: props.categories,\n isUnion: props.isUnion,\n numberOfRowsToStart: offset === 0 ? INITIAL_ITEMS : LOAD_MORE_ITEMS,\n numberOfColumnsToShow: 1,\n getOffset: () => offset,\n});\n\n/**\n * Create image configuration for news entry\n *\n * @param entry - News entry\n * @returns Image config object\n */\nconst createImageConfig = (entry: NewsEntry) => {\n const imageUrl = entry.image?.[0]?.url;\n const altText = entry.image?.[0]?.altText;\n\n if (!imageUrl || !altText) return null;\n\n return {\n imageUrl: imageUrl,\n altText: altText,\n linkUrl: entry.url,\n linkLabel: 'Maryland Today Article with image',\n };\n};\n\n/**\n * Create announcer message\n *\n * @param offset - Current offset\n * @param total - Total entries\n * @param isLazyLoad - Lazy load enabled\n * @returns Announcer message\n */\nconst createAnnouncerMessage = (\n offset: number,\n total: number,\n isLazyLoad: boolean,\n): string => {\n return isLazyLoad\n ? `Showing ${offset} of ${total} articles`\n : `Showing ${offset} articles`;\n};\n\n// ============================================================================\n// STATE MANAGER CLASS\n// ============================================================================\n\n/**\n * Manages featured feed state and shadow DOM synchronization\n *\n * Encapsulates all mutable state including pagination, offset,\n * and shadow DOM management.\n */\nclass FeaturedFeedState {\n private stylesArray: string[] = [];\n private shadowRoot: ShadowRoot | null = null;\n private totalEntries: number = 0;\n private offset: number = 0;\n private hasRenderedOffset: boolean = false;\n private pagination: PaginationState | null = null;\n\n /**\n * Initialize state with initial styles\n *\n * @param initialStyles - Initial CSS styles\n */\n constructor(initialStyles: string) {\n this.stylesArray.push(initialStyles);\n }\n\n /**\n * Add styles to the accumulated styles\n *\n * @param styles - CSS styles to add\n */\n addStyles(styles: string): void {\n this.stylesArray.push(styles);\n }\n\n /**\n * Set shadow root reference for style updates\n *\n * @param shadow - Shadow root element\n */\n setShadowRoot(shadow: ShadowRoot): void {\n this.shadowRoot = shadow;\n }\n\n /**\n * Update shadow DOM styles\n *\n * @returns Promise that resolves when styles are updated\n */\n async updateShadowStyles(): Promise<void> {\n if (!this.shadowRoot) return;\n await styleUtilities.setShadowStyles({\n shadowRoot: this.shadowRoot,\n styles: this.getStyles(),\n });\n }\n\n /**\n * Get accumulated styles as single string\n *\n * @returns Combined CSS styles\n */\n getStyles(): string {\n return this.stylesArray.join('\\n');\n }\n\n /**\n * Get shadow root callback for events\n *\n * @returns Callback function for shadow root\n */\n getShadowCallback(): (shadow: ShadowRoot) => void {\n return (shadow) => this.setShadowRoot(shadow);\n }\n\n /**\n * Get current offset\n *\n * @returns Current offset\n */\n getOffset(): number {\n return this.offset;\n }\n\n /**\n * Set offset to specific value\n *\n * @param value - New offset value\n */\n setOffset(value: number): void {\n this.offset = value;\n }\n\n /**\n * Increment offset by count\n *\n * @param count - Number to increment by\n */\n incrementOffset(count: number): void {\n this.offset += count;\n }\n\n /**\n * Get total entries\n *\n * @returns Total entries\n */\n getTotalEntries(): number {\n return this.totalEntries;\n }\n\n /**\n * Set total entries\n *\n * @param total - Total entries\n */\n setTotalEntries(total: number): void {\n this.totalEntries = total;\n }\n\n /**\n * Check if offset layout has been rendered\n *\n * @returns True if offset layout rendered\n */\n hasOffset(): boolean {\n return this.hasRenderedOffset;\n }\n\n /**\n * Mark offset layout as rendered\n */\n markOffsetRendered(): void {\n this.hasRenderedOffset = true;\n }\n\n /**\n * Get pagination state\n *\n * @returns Pagination state or null\n */\n getPagination(): PaginationState | null {\n return this.pagination;\n }\n\n /**\n * Set pagination state\n *\n * @param pagination - Pagination state\n */\n setPagination(pagination: PaginationState | null): void {\n this.pagination = pagination;\n }\n}\n\n// ============================================================================\n// RENDERING FUNCTIONS\n// ============================================================================\n\n/**\n * Create overlay card for featured entry\n *\n * @param entry - News entry\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @returns Overlay card element model\n */\nconst createOverlayCard = (\n entry: NewsEntry,\n state: FeaturedFeedState,\n isThemeDark: boolean,\n): ElementModel => {\n const overlayCard = newsDisplayStrategy.mapEntryToCard(entry, {\n isOverlay: true,\n isThemeDark,\n imageConfig: () => createImageConfig(entry),\n });\n\n // Add custom overlay styles\n state.addStyles(`\n ${overlayCard.styles}\n\n .${card.overlay.imageClassRef} {\n height: inherit;\n }\n\n @media (${token.media.queries.tablet.min}) {\n .${card.overlay.imageClassRef} .card-overlay-image-container {\n min-height: 560px;\n }\n }\n\n .${card.overlay.imageClassRef} .umd-asset-image-wrapper-scaled {\n position: absolute;\n }\n `);\n\n return overlayCard;\n};\n\n/**\n * Create block cards for entries\n *\n * @param entries - News entries\n * @param state - State manager\n * @param options - Card options\n * @returns Array of block card element models\n */\nconst createBlockCards = (\n entries: NewsEntry[],\n state: FeaturedFeedState,\n options: { isThemeDark: boolean; isTransparent: boolean },\n): ElementModel[] => {\n return entries.map((entry) => {\n const blockCard = newsDisplayStrategy.mapEntryToCard(entry, {\n isThemeDark: options.isThemeDark,\n isTransparent: options.isTransparent,\n isAligned: true,\n imageConfig: () => createImageConfig(entry),\n });\n\n state.addStyles(blockCard.styles);\n return blockCard;\n });\n};\n\n/**\n * Render featured layout (initial load only)\n *\n * @param container - Container element\n * @param entries - News entries\n * @param state - State manager\n * @param props - Feed props\n * @param loadMore - Load more callback\n * @returns Promise that resolves when rendering is complete\n */\nconst renderFeaturedLayout = async (\n container: HTMLElement,\n entries: NewsEntry[],\n state: FeaturedFeedState,\n props: FeaturedProps,\n loadMore: () => Promise<void>,\n): Promise<void> => {\n const {\n isThemeDark = false,\n isTransparent = false,\n isLayoutReversed = false,\n overwriteStickyPosition,\n isLazyLoad = false,\n } = props;\n\n // Fall back to standard grid if not enough entries or already rendered\n if (entries.length < 2 || state.hasOffset()) {\n return renderStandardGrid(container, entries, state, {\n isThemeDark,\n isTransparent,\n });\n }\n\n state.markOffsetRendered();\n\n // Create offset layout\n const offsetLayout = gridOffset({\n columns: 2,\n isLayoutReversed,\n stickyTopPosition: overwriteStickyPosition,\n });\n\n // Create grid for remaining items\n const gridLayout = gridGap({ columns: 2 });\n gridLayout.element.setAttribute('id', 'umd-featured-news-grid-container');\n\n // First item: overlay card\n const overlayCard = createOverlayCard(entries[0], state, isThemeDark);\n\n // Next 2 items: block cards\n const blockCards = createBlockCards(entries.slice(1, 3), state, {\n isThemeDark,\n isTransparent,\n });\n\n // Append block cards to grid\n blockCards.forEach((card) => {\n gridLayout.element.appendChild(card.element);\n });\n\n // Assemble offset layout\n offsetLayout.element.appendChild(overlayCard.element);\n offsetLayout.element.appendChild(gridLayout.element);\n container.appendChild(offsetLayout.element);\n\n state.addStyles(offsetLayout.styles);\n state.addStyles(gridLayout.styles);\n state.setOffset(3); // We've shown 3 items\n\n // Add pagination if needed\n if (isLazyLoad && state.getTotalEntries() > state.getOffset()) {\n const pagination = new PaginationState({\n totalEntries: state.getTotalEntries(),\n offset: state.getOffset(),\n isLazyLoad: true,\n callback: loadMore,\n isThemeDark: options.isThemeDark,\n });\n\n const paginationElement = pagination.render(container);\n if (paginationElement) state.addStyles(paginationElement.styles);\n state.setPagination(pagination);\n }\n\n // Announcer\n const message = createAnnouncerMessage(\n INITIAL_ITEMS,\n state.getTotalEntries(),\n isLazyLoad,\n );\n const announcer = new Announcer({ message });\n container.appendChild(announcer.getElement());\n\n await state.updateShadowStyles();\n};\n\n/**\n * Render standard grid (for lazy-loaded items or fallback)\n *\n * @param container - Container element\n * @param entries - News entries\n * @param state - State manager\n * @param options - Rendering options\n * @returns Promise that resolves when rendering is complete\n */\nconst renderStandardGrid = async (\n container: HTMLElement,\n entries: NewsEntry[],\n state: FeaturedFeedState,\n options: { isThemeDark: boolean; isTransparent: boolean },\n): Promise<void> => {\n let gridContainer = container.querySelector(\n '#umd-featured-news-grid-container',\n ) as HTMLElement;\n\n // Create grid if it doesn't exist\n if (!gridContainer) {\n const gridLayout = gridGap({ columns: 2 });\n gridLayout.element.setAttribute('id', 'umd-featured-news-grid-container');\n container.appendChild(gridLayout.element);\n state.addStyles(gridLayout.styles);\n gridContainer = gridLayout.element;\n }\n\n // Create and append block cards\n const blockCards = createBlockCards(entries, state, options);\n blockCards.forEach((card) => {\n gridContainer.appendChild(card.element);\n });\n\n state.incrementOffset(entries.length);\n\n await state.updateShadowStyles();\n};\n\n/**\n * Render error state\n *\n * @param container - Container element\n * @param message - Error message\n * @param state - State manager\n * @param isThemeDark - Dark theme flag\n * @returns Promise that resolves when rendering is complete\n */\nconst renderError = async (\n container: HTMLElement,\n message: string,\n state: FeaturedFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const emptyState = new EmptyState({ message, isThemeDark });\n emptyState.render(container);\n state.addStyles(emptyState.styles);\n await state.updateShadowStyles();\n};\n\n// ============================================================================\n// MAIN EXPORT\n// ============================================================================\n\n/**\n * Create a featured news feed\n *\n * Displays news with featured layout: overlay card + grid.\n * Uses Element Builder pattern for clean construction.\n *\n * @param props - Feed configuration\n * @returns ElementModel with feed element, styles, and events\n *\n * @example\n * ```typescript\n * const feed = newsFeatured({\n * token: 'my-token',\n * isLazyLoad: true,\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With custom sticky position\n * const feed = newsFeatured({\n * token: 'my-token',\n * overwriteStickyPosition: 100,\n * isLayoutReversed: true,\n * });\n * ```\n */\nexport const newsFeatured = (props: FeaturedProps): ElementModel => {\n const {\n token,\n categories,\n isUnion,\n isThemeDark = false,\n isLazyLoad = false,\n isTransparent = false,\n } = props;\n\n // Create container using ElementBuilder\n const containerBuilder = new ElementBuilder('div').withClassName(\n 'featured-news-feed',\n );\n\n // Get element for manipulation (non-destructive)\n const container = containerBuilder.getElement();\n\n // Initialize state management\n const loading = new LoadingState({ isThemeDark });\n const state = new FeaturedFeedState(loading.styles);\n\n /**\n * Load more articles (for lazy loading)\n */\n const loadMore = async (): Promise<void> => {\n // Remove pagination button\n const pagination = state.getPagination();\n if (pagination) {\n pagination.remove();\n }\n\n // Show loading indicator\n loading.show(container);\n\n // Load more items\n const fetchProps = createFetchProps(\n { token, categories, isUnion },\n state.getOffset(),\n );\n const variables = newsFetchStrategy.composeApiVariables(fetchProps);\n\n const entries = await newsFetchStrategy.fetchEntries(variables);\n\n // Hide loading indicator\n loading.hide();\n\n if (!entries || entries.length === 0) return;\n\n await renderStandardGrid(container, entries, state, {\n isThemeDark,\n isTransparent,\n });\n\n // Update pagination state\n if (pagination) {\n pagination.updateState(state.getOffset(), state.getTotalEntries());\n if (pagination.styles) state.addStyles(pagination.styles);\n await state.updateShadowStyles();\n }\n\n // Update announcer\n const existingAnnouncer = container.querySelector(\n '[role=\"status\"]',\n ) as HTMLElement;\n if (existingAnnouncer) {\n existingAnnouncer.textContent = createAnnouncerMessage(\n state.getOffset(),\n state.getTotalEntries(),\n isLazyLoad,\n );\n }\n\n // Dispatch update event\n eventUtilities.dispatch(\n container,\n eventUtilities.eventNames.FEED_LOADED_MORE,\n {\n items: entries,\n count: entries.length,\n total: state.getTotalEntries(),\n },\n );\n };\n\n /**\n * Initialize feed\n */\n const initialize = async (): Promise<void> => {\n loading.show(container);\n\n // Fetch initial items\n const fetchProps = createFetchProps({ token, categories, isUnion }, 0);\n const variables = newsFetchStrategy.composeApiVariables(fetchProps);\n\n const [count, entries] = await Promise.all([\n newsFetchStrategy.fetchCount(variables),\n newsFetchStrategy.fetchEntries(variables),\n ]);\n\n loading.hide();\n\n // Handle no results\n if (!entries || entries.length === 0) {\n await renderError(\n container,\n 'No news articles found',\n state,\n isThemeDark,\n );\n return;\n }\n\n state.setTotalEntries(count || entries.length);\n\n // Dispatch loaded event\n eventUtilities.dispatch(container, eventUtilities.eventNames.FEED_LOADED, {\n items: entries,\n count: entries.length,\n total: state.getTotalEntries(),\n });\n\n // Render featured layout\n await renderFeaturedLayout(container, entries, state, props, loadMore);\n };\n\n // Start initialization\n initialize();\n\n // Build and return element model\n const model = containerBuilder.build();\n\n // Custom event: allow external control of sticky position\n const setPosition = (position: number) => {\n const overlayElement = container.querySelector(\n `.${card.overlay.imageClassRef}`,\n ) as HTMLElement;\n if (overlayElement) overlayElement.style.top = `${position}px`;\n };\n\n return {\n element: model.element,\n get styles() {\n return state.getStyles();\n },\n events: {\n callback: state.getShadowCallback(),\n setPosition, // Custom event for sticky position control\n },\n };\n};\n"],"names":["styleUtilities.setShadowStyles","options","card","token","eventUtilities.dispatch","eventUtilities.eventNames"],"mappings":";;;;;;;;;;;;AAyCA,MAAM,gBAAgB;AAGtB,MAAM,kBAAkB;AAaxB,MAAM,mBAAmB,CACvB,OACA,YACI;AAAA,EACJ,OAAO,MAAM;AAAA,EACb,YAAY,MAAM;AAAA,EAClB,SAAS,MAAM;AAAA,EACf,qBAAqB,WAAW,IAAI,gBAAgB;AAAA,EACpD,uBAAuB;AAAA,EACvB,WAAW,MAAM;AACnB;AAQA,MAAM,oBAAoB,CAAC,UAAqB;AAC9C,QAAM,WAAW,MAAM,QAAQ,CAAC,GAAG;AACnC,QAAM,UAAU,MAAM,QAAQ,CAAC,GAAG;AAElC,MAAI,CAAC,YAAY,CAAC,QAAS,QAAO;AAElC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,MAAM;AAAA,IACf,WAAW;AAAA,EAAA;AAEf;AAUA,MAAM,yBAAyB,CAC7B,QACA,OACA,eACW;AACX,SAAO,aACH,WAAW,MAAM,OAAO,KAAK,cAC7B,WAAW,MAAM;AACvB;AAYA,MAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAatB,YAAY,eAAuB;AAZnC,SAAQ,cAAwB,CAAA;AAChC,SAAQ,aAAgC;AACxC,SAAQ,eAAuB;AAC/B,SAAQ,SAAiB;AACzB,SAAQ,oBAA6B;AACrC,SAAQ,aAAqC;AAQ3C,SAAK,YAAY,KAAK,aAAa;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAsB;AAC9B,SAAK,YAAY,KAAK,MAAM;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,QAA0B;AACtC,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,qBAAoC;AACxC,QAAI,CAAC,KAAK,WAAY;AACtB,UAAMA,gBAA+B;AAAA,MACnC,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK,UAAA;AAAA,IAAU,CACxB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAoB;AAClB,WAAO,KAAK,YAAY,KAAK,IAAI;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAkD;AAChD,WAAO,CAAC,WAAW,KAAK,cAAc,MAAM;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,OAAqB;AAC7B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB,OAAqB;AACnC,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAA0B;AACxB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB,OAAqB;AACnC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA2B;AACzB,SAAK,oBAAoB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAwC;AACtC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,YAA0C;AACtD,SAAK,aAAa;AAAA,EACpB;AACF;AAcA,MAAM,oBAAoB,CACxB,OACA,OACA,gBACiB;AACjB,QAAM,cAAc,oBAAoB,eAAe,OAAO;AAAA,IAC5D,WAAW;AAAA,IACX;AAAA,IACA,aAAa,MAAM,kBAAkB,KAAK;AAAA,EAAA,CAC3C;AAGD,QAAM,UAAU;AAAA,MACZ,YAAY,MAAM;AAAA;AAAA,OAEjB,KAAK,QAAQ,aAAa;AAAA;AAAA;AAAA;AAAA,cAInB,MAAM,MAAM,QAAQ,OAAO,GAAG;AAAA,SACnC,KAAK,QAAQ,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA,OAK5B,KAAK,QAAQ,aAAa;AAAA;AAAA;AAAA,GAG9B;AAED,SAAO;AACT;AAUA,MAAM,mBAAmB,CACvB,SACA,OACAC,aACmB;AACnB,SAAO,QAAQ,IAAI,CAAC,UAAU;AAC5B,UAAM,YAAY,oBAAoB,eAAe,OAAO;AAAA,MAC1D,aAAaA,SAAQ;AAAA,MACrB,eAAeA,SAAQ;AAAA,MACvB,WAAW;AAAA,MACX,aAAa,MAAM,kBAAkB,KAAK;AAAA,IAAA,CAC3C;AAED,UAAM,UAAU,UAAU,MAAM;AAChC,WAAO;AAAA,EACT,CAAC;AACH;AAYA,MAAM,uBAAuB,OAC3B,WACA,SACA,OACA,OACA,aACkB;AAClB,QAAM;AAAA,IACJ,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB;AAAA,IACA,aAAa;AAAA,EAAA,IACX;AAGJ,MAAI,QAAQ,SAAS,KAAK,MAAM,aAAa;AAC3C,WAAO,mBAAmB,WAAW,SAAS,OAAO;AAAA,MACnD;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAEA,QAAM,mBAAA;AAGN,QAAM,eAAe,WAAW;AAAA,IAC9B,SAAS;AAAA,IACT;AAAA,IACA,mBAAmB;AAAA,EAAA,CACpB;AAGD,QAAM,aAAa,QAAQ,EAAE,SAAS,GAAG;AACzC,aAAW,QAAQ,aAAa,MAAM,kCAAkC;AAGxE,QAAM,cAAc,kBAAkB,QAAQ,CAAC,GAAG,OAAO,WAAW;AAGpE,QAAM,aAAa,iBAAiB,QAAQ,MAAM,GAAG,CAAC,GAAG,OAAO;AAAA,IAC9D;AAAA,IACA;AAAA,EAAA,CACD;AAGD,aAAW,QAAQ,CAACC,UAAS;AAC3B,eAAW,QAAQ,YAAYA,MAAK,OAAO;AAAA,EAC7C,CAAC;AAGD,eAAa,QAAQ,YAAY,YAAY,OAAO;AACpD,eAAa,QAAQ,YAAY,WAAW,OAAO;AACnD,YAAU,YAAY,aAAa,OAAO;AAE1C,QAAM,UAAU,aAAa,MAAM;AACnC,QAAM,UAAU,WAAW,MAAM;AACjC,QAAM,UAAU,CAAC;AAGjB,MAAI,cAAc,MAAM,gBAAA,IAAoB,MAAM,aAAa;AAC7D,UAAM,aAAa,IAAI,gBAAgB;AAAA,MACrC,cAAc,MAAM,gBAAA;AAAA,MACpB,QAAQ,MAAM,UAAA;AAAA,MACd,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,aAAa,QAAQ;AAAA,IAAA,CACtB;AAED,UAAM,oBAAoB,WAAW,OAAO,SAAS;AACrD,QAAI,kBAAmB,OAAM,UAAU,kBAAkB,MAAM;AAC/D,UAAM,cAAc,UAAU;AAAA,EAChC;AAGA,QAAM,UAAU;AAAA,IACd;AAAA,IACA,MAAM,gBAAA;AAAA,IACN;AAAA,EAAA;AAEF,QAAM,YAAY,IAAI,UAAU,EAAE,SAAS;AAC3C,YAAU,YAAY,UAAU,YAAY;AAE5C,QAAM,MAAM,mBAAA;AACd;AAWA,MAAM,qBAAqB,OACzB,WACA,SACA,OACAD,aACkB;AAClB,MAAI,gBAAgB,UAAU;AAAA,IAC5B;AAAA,EAAA;AAIF,MAAI,CAAC,eAAe;AAClB,UAAM,aAAa,QAAQ,EAAE,SAAS,GAAG;AACzC,eAAW,QAAQ,aAAa,MAAM,kCAAkC;AACxE,cAAU,YAAY,WAAW,OAAO;AACxC,UAAM,UAAU,WAAW,MAAM;AACjC,oBAAgB,WAAW;AAAA,EAC7B;AAGA,QAAM,aAAa,iBAAiB,SAAS,OAAOA,QAAO;AAC3D,aAAW,QAAQ,CAACC,UAAS;AAC3B,kBAAc,YAAYA,MAAK,OAAO;AAAA,EACxC,CAAC;AAED,QAAM,gBAAgB,QAAQ,MAAM;AAEpC,QAAM,MAAM,mBAAA;AACd;AAWA,MAAM,cAAc,OAClB,WACA,SACA,OACA,gBACkB;AAClB,QAAM,aAAa,IAAI,WAAW,EAAE,SAAS,aAAa;AAC1D,aAAW,OAAO,SAAS;AAC3B,QAAM,UAAU,WAAW,MAAM;AACjC,QAAM,MAAM,mBAAA;AACd;AAiCO,MAAM,eAAe,CAAC,UAAuC;AAClE,QAAM;AAAA,IACJ,OAAAC;AAAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,aAAa;AAAA,IACb,gBAAgB;AAAA,EAAA,IACd;AAGJ,QAAM,mBAAmB,IAAI,eAAe,KAAK,EAAE;AAAA,IACjD;AAAA,EAAA;AAIF,QAAM,YAAY,iBAAiB,WAAA;AAGnC,QAAM,UAAU,IAAI,aAAa,EAAE,aAAa;AAChD,QAAM,QAAQ,IAAI,kBAAkB,QAAQ,MAAM;AAKlD,QAAM,WAAW,YAA2B;AAE1C,UAAM,aAAa,MAAM,cAAA;AACzB,QAAI,YAAY;AACd,iBAAW,OAAA;AAAA,IACb;AAGA,YAAQ,KAAK,SAAS;AAGtB,UAAM,aAAa;AAAA,MACjB,EAAE,OAAAA,QAAO,YAAY,QAAA;AAAA,MACrB,MAAM,UAAA;AAAA,IAAU;AAElB,UAAM,YAAY,kBAAkB,oBAAoB,UAAU;AAElE,UAAM,UAAU,MAAM,kBAAkB,aAAa,SAAS;AAG9D,YAAQ,KAAA;AAER,QAAI,CAAC,WAAW,QAAQ,WAAW,EAAG;AAEtC,UAAM,mBAAmB,WAAW,SAAS,OAAO;AAAA,MAClD;AAAA,MACA;AAAA,IAAA,CACD;AAGD,QAAI,YAAY;AACd,iBAAW,YAAY,MAAM,UAAA,GAAa,MAAM,iBAAiB;AACjE,UAAI,WAAW,OAAQ,OAAM,UAAU,WAAW,MAAM;AACxD,YAAM,MAAM,mBAAA;AAAA,IACd;AAGA,UAAM,oBAAoB,UAAU;AAAA,MAClC;AAAA,IAAA;AAEF,QAAI,mBAAmB;AACrB,wBAAkB,cAAc;AAAA,QAC9B,MAAM,UAAA;AAAA,QACN,MAAM,gBAAA;AAAA,QACN;AAAA,MAAA;AAAA,IAEJ;AAGAC;AAAAA,MACE;AAAA,MACAC,WAA0B;AAAA,MAC1B;AAAA,QACE,OAAO;AAAA,QACP,OAAO,QAAQ;AAAA,QACf,OAAO,MAAM,gBAAA;AAAA,MAAgB;AAAA,IAC/B;AAAA,EAEJ;AAKA,QAAM,aAAa,YAA2B;AAC5C,YAAQ,KAAK,SAAS;AAGtB,UAAM,aAAa,iBAAiB,EAAE,OAAAF,QAAO,YAAY,QAAA,GAAW,CAAC;AACrE,UAAM,YAAY,kBAAkB,oBAAoB,UAAU;AAElE,UAAM,CAAC,OAAO,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MACzC,kBAAkB,WAAW,SAAS;AAAA,MACtC,kBAAkB,aAAa,SAAS;AAAA,IAAA,CACzC;AAED,YAAQ,KAAA;AAGR,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAEF;AAAA,IACF;AAEA,UAAM,gBAAgB,SAAS,QAAQ,MAAM;AAG7CC,aAAwB,WAAWC,WAA0B,aAAa;AAAA,MACxE,OAAO;AAAA,MACP,OAAO,QAAQ;AAAA,MACf,OAAO,MAAM,gBAAA;AAAA,IAAgB,CAC9B;AAGD,UAAM,qBAAqB,WAAW,SAAS,OAAO,OAAO,QAAQ;AAAA,EACvE;AAGA,aAAA;AAGA,QAAM,QAAQ,iBAAiB,MAAA;AAG/B,QAAM,cAAc,CAAC,aAAqB;AACxC,UAAM,iBAAiB,UAAU;AAAA,MAC/B,IAAI,KAAK,QAAQ,aAAa;AAAA,IAAA;AAEhC,QAAI,eAAgB,gBAAe,MAAM,MAAM,GAAG,QAAQ;AAAA,EAC5D;AAEA,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,IAAI,SAAS;AACX,aAAO,MAAM,UAAA;AAAA,IACf;AAAA,IACA,QAAQ;AAAA,MACN,UAAU,MAAM,kBAAA;AAAA,MAChB;AAAA;AAAA,IAAA;AAAA,EACF;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../source/states/pagination.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,qBAAqB,EAAkB,MAAM,UAAU,CAAC;AAsBtE,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,qBAAqB,GAC5B,YAAY,GAAG,SAAS,CAuC1B;AAyBD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,SAAS,CAA4B;gBAEjC,MAAM,EAAE,qBAAqB;IASzC,MAAM,CAAC,SAAS,EAAE,WAAW,GAAG,YAAY,GAAG,SAAS;IAWxD,MAAM,IAAI,IAAI;IAUd,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAmBvD,OAAO,IAAI,OAAO;IAQlB,OAAO,IAAI,IAAI;IAQf,IAAI,OAAO,IAAI,WAAW,GAAG,SAAS,CAErC;IAKD,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;CACF"}
1
+ {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../source/states/pagination.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,qBAAqB,EAAkB,MAAM,UAAU,CAAC;AAsBtE,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,qBAAqB,GAC5B,YAAY,GAAG,SAAS,CAyC1B;AAyBD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,SAAS,CAA4B;gBAEjC,MAAM,EAAE,qBAAqB;IASzC,MAAM,CAAC,SAAS,EAAE,WAAW,GAAG,YAAY,GAAG,SAAS;IAWxD,MAAM,IAAI,IAAI;IAUd,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAmBvD,OAAO,IAAI,OAAO;IAQlB,OAAO,IAAI,IAAI;IAQf,IAAI,OAAO,IAAI,WAAW,GAAG,SAAS,CAErC;IAKD,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;CACF"}
@@ -2,7 +2,7 @@ import * as Styles from "@universityofmaryland/web-styles-library";
2
2
  import { ElementBuilder } from "@universityofmaryland/web-builder-library";
3
3
  import { FeedStateEvent } from "./_types.js";
4
4
  function createPaginationElement(config) {
5
- const { callback, isLazyLoad, totalEntries, offset } = config;
5
+ const { callback, isLazyLoad, isThemeDark, totalEntries, offset } = config;
6
6
  if (!isLazyLoad) return;
7
7
  if (!totalEntries) return;
8
8
  if (!offset) return;
@@ -23,7 +23,8 @@ function createPaginationElement(config) {
23
23
  })
24
24
  );
25
25
  });
26
- const ctaButton = new ElementBuilder(button).styled(Styles.element.action.outline.normal).build();
26
+ const buttonStyle = isThemeDark ? Styles.element.action.outline.white : Styles.element.action.outline.normal;
27
+ const ctaButton = new ElementBuilder(button).styled(buttonStyle).build();
27
28
  return new ElementBuilder().styled(Styles.layout.alignment.block.center).withChild(ctaButton).withStyles({
28
29
  element: {
29
30
  marginTop: `${Styles.token.spacing.lg}`
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.js","sources":["../../source/states/pagination.ts"],"sourcesContent":["import * as Styles from '@universityofmaryland/web-styles-library';\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\n\nimport { type ElementModel } from '../_types';\nimport { type PaginationStateConfig, FeedStateEvent } from './_types';\n\n/**\n * Creates a pagination \"Load more\" button element\n *\n * @param config - Pagination state configuration\n * @returns ElementModel with load more button, or undefined if pagination not needed\n *\n * @example\n * ```typescript\n * const pagination = createPaginationElement({\n * callback: () => loadMoreItems(),\n * isThemeDark: false,\n * isLazyLoad: true,\n * totalEntries: 50,\n * offset: 10\n * });\n * if (pagination) {\n * container.appendChild(pagination.element);\n * }\n * ```\n */\nexport function createPaginationElement(\n config: PaginationStateConfig\n): ElementModel | undefined {\n const { callback, isLazyLoad, totalEntries, offset } = config;\n\n // Guard clauses for when pagination is not needed\n if (!isLazyLoad) return;\n if (!totalEntries) return;\n if (!offset) return;\n if (!callback) return;\n if (offset >= totalEntries) return;\n\n const button = document.createElement('button');\n button.textContent = 'Load more'; // Use textContent for security\n button.addEventListener('click', () => {\n callback();\n button.dispatchEvent(\n new CustomEvent(FeedStateEvent.PAGINATION_LOADED, {\n bubbles: true,\n detail: {\n offset,\n totalEntries,\n timestamp: Date.now(),\n },\n })\n );\n });\n\n const ctaButton = new ElementBuilder(button)\n .styled(Styles.element.action.outline.normal)\n .build();\n\n return new ElementBuilder()\n .styled(Styles.layout.alignment.block.center)\n .withChild(ctaButton)\n .withStyles({\n element: {\n marginTop: `${Styles.token.spacing.lg}`,\n },\n })\n .build();\n}\n\n/**\n * Pagination state manager class\n *\n * Manages a \"Load more\" button for paginated feed content.\n *\n * @example\n * ```typescript\n * const pagination = new PaginationState({\n * callback: () => loadMore(),\n * isLazyLoad: true,\n * totalEntries: 50,\n * offset: 10\n * });\n *\n * const button = pagination.render(container);\n * if (button) {\n * // Pagination was rendered\n * }\n *\n * // Later, update state\n * pagination.updateState(20, 50);\n * ```\n */\nexport class PaginationState {\n private config: PaginationStateConfig;\n private model: ElementModel | undefined;\n private container: HTMLElement | null = null;\n\n constructor(config: PaginationStateConfig) {\n this.config = config;\n this.model = createPaginationElement(config);\n }\n\n /**\n * Render the pagination button if applicable\n * @returns ElementModel if rendered, undefined otherwise\n */\n render(container: HTMLElement): ElementModel | undefined {\n if (this.model) {\n this.container = container;\n container.appendChild(this.model.element);\n }\n return this.model;\n }\n\n /**\n * Remove the pagination button from DOM\n */\n remove(): void {\n if (this.model && this.model.element.parentNode) {\n this.model.element.remove();\n }\n }\n\n /**\n * Update pagination state with new offset and total\n * Re-creates button if needed\n */\n updateState(offset: number, totalEntries: number): void {\n this.config.offset = offset;\n this.config.totalEntries = totalEntries;\n\n // Remove old button\n this.remove();\n\n // Create new button if still needed\n this.model = createPaginationElement(this.config);\n\n // Re-render if we have a container\n if (this.container && this.model) {\n this.container.appendChild(this.model.element);\n }\n }\n\n /**\n * Check if more items are available to load\n */\n hasMore(): boolean {\n const { offset, totalEntries } = this.config;\n return !!(totalEntries && offset < totalEntries);\n }\n\n /**\n * Cleanup and remove pagination\n */\n destroy(): void {\n this.remove();\n this.container = null;\n }\n\n /**\n * Get the pagination element if it exists\n */\n get element(): HTMLElement | undefined {\n return this.model?.element;\n }\n\n /**\n * Get the pagination styles if element exists\n */\n get styles(): string | undefined {\n return this.model?.styles;\n }\n}\n\n"],"names":[],"mappings":";;;AA0BO,SAAS,wBACd,QAC0B;AAC1B,QAAM,EAAE,UAAU,YAAY,cAAc,WAAW;AAGvD,MAAI,CAAC,WAAY;AACjB,MAAI,CAAC,aAAc;AACnB,MAAI,CAAC,OAAQ;AACb,MAAI,CAAC,SAAU;AACf,MAAI,UAAU,aAAc;AAE5B,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,cAAc;AACrB,SAAO,iBAAiB,SAAS,MAAM;AACrC,aAAA;AACA,WAAO;AAAA,MACL,IAAI,YAAY,eAAe,mBAAmB;AAAA,QAChD,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,UACA;AAAA,UACA,WAAW,KAAK,IAAA;AAAA,QAAI;AAAA,MACtB,CACD;AAAA,IAAA;AAAA,EAEL,CAAC;AAED,QAAM,YAAY,IAAI,eAAe,MAAM,EACxC,OAAO,OAAO,QAAQ,OAAO,QAAQ,MAAM,EAC3C,MAAA;AAEH,SAAO,IAAI,eAAA,EACR,OAAO,OAAO,OAAO,UAAU,MAAM,MAAM,EAC3C,UAAU,SAAS,EACnB,WAAW;AAAA,IACV,SAAS;AAAA,MACP,WAAW,GAAG,OAAO,MAAM,QAAQ,EAAE;AAAA,IAAA;AAAA,EACvC,CACD,EACA,MAAA;AACL;AAyBO,MAAM,gBAAgB;AAAA,EAK3B,YAAY,QAA+B;AAF3C,SAAQ,YAAgC;AAGtC,SAAK,SAAS;AACd,SAAK,QAAQ,wBAAwB,MAAM;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,WAAkD;AACvD,QAAI,KAAK,OAAO;AACd,WAAK,YAAY;AACjB,gBAAU,YAAY,KAAK,MAAM,OAAO;AAAA,IAC1C;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAe;AACb,QAAI,KAAK,SAAS,KAAK,MAAM,QAAQ,YAAY;AAC/C,WAAK,MAAM,QAAQ,OAAA;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,QAAgB,cAA4B;AACtD,SAAK,OAAO,SAAS;AACrB,SAAK,OAAO,eAAe;AAG3B,SAAK,OAAA;AAGL,SAAK,QAAQ,wBAAwB,KAAK,MAAM;AAGhD,QAAI,KAAK,aAAa,KAAK,OAAO;AAChC,WAAK,UAAU,YAAY,KAAK,MAAM,OAAO;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AACjB,UAAM,EAAE,QAAQ,aAAA,IAAiB,KAAK;AACtC,WAAO,CAAC,EAAE,gBAAgB,SAAS;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,OAAA;AACL,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAmC;AACrC,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAA6B;AAC/B,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;"}
1
+ {"version":3,"file":"pagination.js","sources":["../../source/states/pagination.ts"],"sourcesContent":["import * as Styles from '@universityofmaryland/web-styles-library';\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\n\nimport { type ElementModel } from '../_types';\nimport { type PaginationStateConfig, FeedStateEvent } from './_types';\n\n/**\n * Creates a pagination \"Load more\" button element\n *\n * @param config - Pagination state configuration\n * @returns ElementModel with load more button, or undefined if pagination not needed\n *\n * @example\n * ```typescript\n * const pagination = createPaginationElement({\n * callback: () => loadMoreItems(),\n * isThemeDark: false,\n * isLazyLoad: true,\n * totalEntries: 50,\n * offset: 10\n * });\n * if (pagination) {\n * container.appendChild(pagination.element);\n * }\n * ```\n */\nexport function createPaginationElement(\n config: PaginationStateConfig,\n): ElementModel | undefined {\n const { callback, isLazyLoad, isThemeDark, totalEntries, offset } = config;\n\n // Guard clauses for when pagination is not needed\n if (!isLazyLoad) return;\n if (!totalEntries) return;\n if (!offset) return;\n if (!callback) return;\n if (offset >= totalEntries) return;\n\n const button = document.createElement('button');\n button.textContent = 'Load more'; // Use textContent for security\n button.addEventListener('click', () => {\n callback();\n button.dispatchEvent(\n new CustomEvent(FeedStateEvent.PAGINATION_LOADED, {\n bubbles: true,\n detail: {\n offset,\n totalEntries,\n timestamp: Date.now(),\n },\n }),\n );\n });\n\n const buttonStyle = isThemeDark\n ? Styles.element.action.outline.white\n : Styles.element.action.outline.normal;\n\n const ctaButton = new ElementBuilder(button).styled(buttonStyle).build();\n\n return new ElementBuilder()\n .styled(Styles.layout.alignment.block.center)\n .withChild(ctaButton)\n .withStyles({\n element: {\n marginTop: `${Styles.token.spacing.lg}`,\n },\n })\n .build();\n}\n\n/**\n * Pagination state manager class\n *\n * Manages a \"Load more\" button for paginated feed content.\n *\n * @example\n * ```typescript\n * const pagination = new PaginationState({\n * callback: () => loadMore(),\n * isLazyLoad: true,\n * totalEntries: 50,\n * offset: 10\n * });\n *\n * const button = pagination.render(container);\n * if (button) {\n * // Pagination was rendered\n * }\n *\n * // Later, update state\n * pagination.updateState(20, 50);\n * ```\n */\nexport class PaginationState {\n private config: PaginationStateConfig;\n private model: ElementModel | undefined;\n private container: HTMLElement | null = null;\n\n constructor(config: PaginationStateConfig) {\n this.config = config;\n this.model = createPaginationElement(config);\n }\n\n /**\n * Render the pagination button if applicable\n * @returns ElementModel if rendered, undefined otherwise\n */\n render(container: HTMLElement): ElementModel | undefined {\n if (this.model) {\n this.container = container;\n container.appendChild(this.model.element);\n }\n return this.model;\n }\n\n /**\n * Remove the pagination button from DOM\n */\n remove(): void {\n if (this.model && this.model.element.parentNode) {\n this.model.element.remove();\n }\n }\n\n /**\n * Update pagination state with new offset and total\n * Re-creates button if needed\n */\n updateState(offset: number, totalEntries: number): void {\n this.config.offset = offset;\n this.config.totalEntries = totalEntries;\n\n // Remove old button\n this.remove();\n\n // Create new button if still needed\n this.model = createPaginationElement(this.config);\n\n // Re-render if we have a container\n if (this.container && this.model) {\n this.container.appendChild(this.model.element);\n }\n }\n\n /**\n * Check if more items are available to load\n */\n hasMore(): boolean {\n const { offset, totalEntries } = this.config;\n return !!(totalEntries && offset < totalEntries);\n }\n\n /**\n * Cleanup and remove pagination\n */\n destroy(): void {\n this.remove();\n this.container = null;\n }\n\n /**\n * Get the pagination element if it exists\n */\n get element(): HTMLElement | undefined {\n return this.model?.element;\n }\n\n /**\n * Get the pagination styles if element exists\n */\n get styles(): string | undefined {\n return this.model?.styles;\n }\n}\n"],"names":[],"mappings":";;;AA0BO,SAAS,wBACd,QAC0B;AAC1B,QAAM,EAAE,UAAU,YAAY,aAAa,cAAc,WAAW;AAGpE,MAAI,CAAC,WAAY;AACjB,MAAI,CAAC,aAAc;AACnB,MAAI,CAAC,OAAQ;AACb,MAAI,CAAC,SAAU;AACf,MAAI,UAAU,aAAc;AAE5B,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,cAAc;AACrB,SAAO,iBAAiB,SAAS,MAAM;AACrC,aAAA;AACA,WAAO;AAAA,MACL,IAAI,YAAY,eAAe,mBAAmB;AAAA,QAChD,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,UACA;AAAA,UACA,WAAW,KAAK,IAAA;AAAA,QAAI;AAAA,MACtB,CACD;AAAA,IAAA;AAAA,EAEL,CAAC;AAED,QAAM,cAAc,cAChB,OAAO,QAAQ,OAAO,QAAQ,QAC9B,OAAO,QAAQ,OAAO,QAAQ;AAElC,QAAM,YAAY,IAAI,eAAe,MAAM,EAAE,OAAO,WAAW,EAAE,MAAA;AAEjE,SAAO,IAAI,eAAA,EACR,OAAO,OAAO,OAAO,UAAU,MAAM,MAAM,EAC3C,UAAU,SAAS,EACnB,WAAW;AAAA,IACV,SAAS;AAAA,MACP,WAAW,GAAG,OAAO,MAAM,QAAQ,EAAE;AAAA,IAAA;AAAA,EACvC,CACD,EACA,MAAA;AACL;AAyBO,MAAM,gBAAgB;AAAA,EAK3B,YAAY,QAA+B;AAF3C,SAAQ,YAAgC;AAGtC,SAAK,SAAS;AACd,SAAK,QAAQ,wBAAwB,MAAM;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,WAAkD;AACvD,QAAI,KAAK,OAAO;AACd,WAAK,YAAY;AACjB,gBAAU,YAAY,KAAK,MAAM,OAAO;AAAA,IAC1C;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAe;AACb,QAAI,KAAK,SAAS,KAAK,MAAM,QAAQ,YAAY;AAC/C,WAAK,MAAM,QAAQ,OAAA;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,QAAgB,cAA4B;AACtD,SAAK,OAAO,SAAS;AACrB,SAAK,OAAO,eAAe;AAG3B,SAAK,OAAA;AAGL,SAAK,QAAQ,wBAAwB,KAAK,MAAM;AAGhD,QAAI,KAAK,aAAa,KAAK,OAAO;AAChC,WAAK,UAAU,YAAY,KAAK,MAAM,OAAO;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AACjB,UAAM,EAAE,QAAQ,aAAA,IAAiB,KAAK;AACtC,WAAO,CAAC,EAAE,gBAAgB,SAAS;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,OAAA;AACL,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAmC;AACrC,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAA6B;AAC/B,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;"}
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../source/strategies/fetch/events.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA+IxC,eAAO,MAAM,kBAAkB,QAA0B,CAAC;AAC1D,eAAO,MAAM,YAAY,QAAqB,CAAC;AAM/C,eAAO,MAAM,oBAAoB,0HAOhC,CAAC;AAMF,eAAO,MAAM,mBAAmB,QAc/B,CAAC;AAqDF,iBAAe,kBAAkB,CAC/B,WAAW,EAAE,MAAM,EAAE,EACrB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAkB1B;AAED,eAAO,MAAM,mBAAmB;;;;;CAG/B,CAAC;AAyCF,eAAO,MAAM,wBAAwB;;;;;CAGpC,CAAC"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../source/strategies/fetch/events.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAmJxC,eAAO,MAAM,kBAAkB,QAA0B,CAAC;AAC1D,eAAO,MAAM,YAAY,QAAqB,CAAC;AAM/C,eAAO,MAAM,oBAAoB,0HAOhC,CAAC;AAMF,eAAO,MAAM,mBAAmB,QAc/B,CAAC;AAqDF,iBAAe,kBAAkB,CAC/B,WAAW,EAAE,MAAM,EAAE,EACrB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAkB1B;AAED,eAAO,MAAM,mBAAmB;;;;;CAG/B,CAAC;AAyCF,eAAO,MAAM,wBAAwB;;;;;CAGpC,CAAC"}
@@ -27,7 +27,7 @@ function generateFragments() {
27
27
  summary: commonRichTextTwo
28
28
  image: commonAssetHeroImageSingle {
29
29
  url
30
- altText: commonPlainTextTwo
30
+ alt
31
31
  }
32
32
  location: categoriesCampusBuildingSingle {
33
33
  title
@@ -70,6 +70,10 @@ function generateSliderFragments() {
70
70
  fragment EventSliderFields_${cal} on ${cal}_Event {
71
71
  title
72
72
  url
73
+ image: commonAssetHeroImageSingle {
74
+ url
75
+ alt
76
+ }
73
77
  startMonth: startDate @formatDateTime(format: "M")
74
78
  startDay: startDate @formatDateTime(format: "d")
75
79
  endMonth: endDate @formatDateTime(format: "M")
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sources":["../../../source/strategies/fetch/events.ts"],"sourcesContent":["/**\n * Events Fetch Strategy\n *\n * Strategy for fetching event data from the UMD Calendar GraphQL API.\n *\n * @module strategies/fetch/events\n */\n\nimport { createGraphQLFetchStrategy } from './graphql';\nimport { EventEntry } from 'types/data';\nimport { fetchGraphQL } from '@universityofmaryland/web-utilities-library/network';\n\n/**\n * Calendar types available in the UMD Calendar system\n */\nconst CALENDARS = ['communications', 'submission'] as const;\n\n/**\n * Generate GraphQL fragments for all calendar types\n * Creates type-specific fragments to avoid interface limitations\n */\nfunction generateFragments() {\n return CALENDARS.map(\n (cal) => `\n fragment EventBasicFields_${cal} on ${cal}_Event {\n id\n title\n url\n }\n\n fragment EventDateFields_${cal} on ${cal}_Event {\n startDayOfWeek: startDate @formatDateTime(format: \"D\")\n startMonth: startDate @formatDateTime(format: \"M\")\n startDay: startDate @formatDateTime(format: \"d\")\n startStamp: startDate @formatDateTime(format: \"Y-m-d\")\n startTime: startDate @formatDateTime(format: \"g:ia\")\n endDayOfWeek: endDate @formatDateTime(format: \"D\")\n endMonth: endDate @formatDateTime(format: \"M\")\n endDay: endDate @formatDateTime(format: \"d\")\n endTime: endDate @formatDateTime(format: \"g:ia\")\n allDay\n }\n\n fragment EventContentFields_${cal} on ${cal}_Event {\n summary: commonRichTextTwo\n image: commonAssetHeroImageSingle {\n url\n altText: commonPlainTextTwo\n }\n location: categoriesCampusBuildingSingle {\n title\n }\n }\n\n fragment EventCategoryFields_${cal} on ${cal}_Event {\n categories: categoriesEventAudienceMultiple {\n id\n title\n }\n }\n `,\n ).join('\\n');\n}\n\n/**\n * Generate inline fragment spreads for all calendar types\n * Used in the main query to apply fragments to each calendar type\n */\nfunction generateInlineSpreads() {\n return CALENDARS.map(\n (cal) => `\n ... on ${cal}_Event {\n ...EventBasicFields_${cal}\n ...EventDateFields_${cal}\n ...EventContentFields_${cal}\n ...EventCategoryFields_${cal}\n }\n `,\n ).join('\\n');\n}\n\n/**\n * Generate inline fragment spreads for slider queries\n * Minimal fields for carousel displays\n */\nfunction generateSliderSpreads() {\n return CALENDARS.map(\n (cal) => `\n ... on ${cal}_Event {\n ...EventSliderFields_${cal}\n }\n `,\n ).join('\\n');\n}\n\n/**\n * Generate slider-specific fragments\n */\nfunction generateSliderFragments() {\n return CALENDARS.map(\n (cal) => `\n fragment EventSliderFields_${cal} on ${cal}_Event {\n title\n url\n startMonth: startDate @formatDateTime(format: \"M\")\n startDay: startDate @formatDateTime(format: \"d\")\n endMonth: endDate @formatDateTime(format: \"M\")\n endDay: endDate @formatDateTime(format: \"d\")\n }\n `,\n ).join('\\n');\n}\n\n/**\n * GraphQL queries for events\n */\nconst buildEventsCountQuery = (\n dateFilter: 'startsAfterOrAt' | 'rangeStart' = 'startsAfterOrAt',\n) => `\n query getEventsCount($startDate: String!, $related: [QueryArgument]) {\n count: solspace_calendar {\n events(relatedTo: $related, loadOccurrences: true, ${dateFilter}: $startDate) {\n ... on communications_Event {\n id\n }\n ... on submission_Event {\n id\n }\n }\n }\n }\n`;\n\nconst buildEventsQuery = (\n dateFilter: 'startsAfterOrAt' | 'rangeStart' = 'startsAfterOrAt',\n) => `\n query getEvents($startDate: String!, $related: [QueryArgument], $limit: Int, $offset: Int) {\n entries: solspace_calendar {\n events(\n relatedTo: $related\n loadOccurrences: true\n ${dateFilter}: $startDate\n limit: $limit\n offset: $offset\n ) {\n ${generateInlineSpreads()}\n }\n }\n }\n ${generateFragments()}\n`;\n\nexport const EVENTS_COUNT_QUERY = buildEventsCountQuery();\nexport const EVENTS_QUERY = buildEventsQuery();\n\n/**\n * Query for fetching category names by IDs\n * Queries the categories table directly\n */\nexport const CATEGORY_NAMES_QUERY = `\n query getCategoryNames($ids: [QueryArgument]!) {\n categories(id: $ids) {\n id\n title\n }\n }\n`;\n\n/**\n * Slider-specific query with minimal fields\n * Used by events slider for carousel displays\n */\nexport const EVENTS_SLIDER_QUERY = `\n query getEvents($startDate: String!, $related: [QueryArgument]) {\n entries: solspace_calendar {\n events(\n relatedTo: $related\n loadOccurrences: true\n startsAfterOrAt: $startDate\n limit: 12\n ) {\n ${generateSliderSpreads()}\n }\n }\n }\n ${generateSliderFragments()}\n`;\n\n/**\n * Events fetch strategy\n *\n * Fetches event data from the UMD Calendar GraphQL API.\n * Handles date filtering, category filtering, and pagination.\n *\n * @example\n * ```typescript\n * const feed = createBaseFeed({\n * token: 'my-token',\n * fetchStrategy: eventsFetchStrategy,\n * // ...\n * });\n * ```\n */\nconst baseFetchStrategy = createGraphQLFetchStrategy<EventEntry>({\n endpoint: 'https://calendar.umd.edu/graphql',\n\n queries: {\n entries: EVENTS_QUERY,\n count: EVENTS_COUNT_QUERY,\n },\n\n transformResponse: (data) => {\n if (!data || !data.data || !data.data.entries) {\n return null;\n }\n return data.data.entries.events || null;\n },\n\n transformCount: (data) => {\n if (!data || !data.data || !data.data.count) {\n return 0;\n }\n return data.data.count.events?.length || 0;\n },\n\n composeVariables: (baseVariables) => {\n const { categories, ...rest } = baseVariables;\n\n return {\n ...rest,\n related: categories,\n startDate: new Date().toDateString(),\n };\n },\n});\n\n/**\n * Fetch category names by their IDs\n */\nasync function fetchCategoryNames(\n categoryIds: string[],\n token?: string,\n): Promise<string[] | null> {\n try {\n const response = await fetchGraphQL({\n url: 'https://calendar.umd.edu/graphql',\n query: CATEGORY_NAMES_QUERY,\n token: token || '',\n variables: { ids: categoryIds },\n });\n\n if (!response || !response.data || !response.data.categories) {\n return null;\n }\n\n return response.data.categories.map((category: any) => category.title);\n } catch (error) {\n console.error('Fetch category names error:', error);\n return null;\n }\n}\n\nexport const eventsFetchStrategy = {\n ...baseFetchStrategy,\n fetchCategoryNames,\n};\n\n/**\n * Events fetch strategy for range queries\n *\n * Uses rangeStart filter instead of startsAfterOrAt.\n * Used for grouped event displays.\n */\nconst baseFetchStrategyRange = createGraphQLFetchStrategy<EventEntry>({\n endpoint: 'https://calendar.umd.edu/graphql',\n\n queries: {\n entries: buildEventsQuery('rangeStart'),\n count: buildEventsCountQuery('rangeStart'),\n },\n\n transformResponse: (data) => {\n if (!data || !data.data || !data.data.entries) {\n return null;\n }\n return data.data.entries.events || null;\n },\n\n transformCount: (data) => {\n if (!data || !data.data || !data.data.count) {\n return 0;\n }\n return data.data.count.events?.length || 0;\n },\n\n composeVariables: (baseVariables) => {\n const { categories, ...rest } = baseVariables;\n\n return {\n ...rest,\n related: categories,\n startDate: new Date().toDateString(),\n };\n },\n});\n\nexport const eventsFetchStrategyRange = {\n ...baseFetchStrategyRange,\n fetchCategoryNames,\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"events.js","sources":["../../../source/strategies/fetch/events.ts"],"sourcesContent":["/**\n * Events Fetch Strategy\n *\n * Strategy for fetching event data from the UMD Calendar GraphQL API.\n *\n * @module strategies/fetch/events\n */\n\nimport { createGraphQLFetchStrategy } from './graphql';\nimport { EventEntry } from 'types/data';\nimport { fetchGraphQL } from '@universityofmaryland/web-utilities-library/network';\n\n/**\n * Calendar types available in the UMD Calendar system\n */\nconst CALENDARS = ['communications', 'submission'] as const;\n\n/**\n * Generate GraphQL fragments for all calendar types\n * Creates type-specific fragments to avoid interface limitations\n */\nfunction generateFragments() {\n return CALENDARS.map(\n (cal) => `\n fragment EventBasicFields_${cal} on ${cal}_Event {\n id\n title\n url\n }\n\n fragment EventDateFields_${cal} on ${cal}_Event {\n startDayOfWeek: startDate @formatDateTime(format: \"D\")\n startMonth: startDate @formatDateTime(format: \"M\")\n startDay: startDate @formatDateTime(format: \"d\")\n startStamp: startDate @formatDateTime(format: \"Y-m-d\")\n startTime: startDate @formatDateTime(format: \"g:ia\")\n endDayOfWeek: endDate @formatDateTime(format: \"D\")\n endMonth: endDate @formatDateTime(format: \"M\")\n endDay: endDate @formatDateTime(format: \"d\")\n endTime: endDate @formatDateTime(format: \"g:ia\")\n allDay\n }\n\n fragment EventContentFields_${cal} on ${cal}_Event {\n summary: commonRichTextTwo\n image: commonAssetHeroImageSingle {\n url\n alt\n }\n location: categoriesCampusBuildingSingle {\n title\n }\n }\n\n fragment EventCategoryFields_${cal} on ${cal}_Event {\n categories: categoriesEventAudienceMultiple {\n id\n title\n }\n }\n `,\n ).join('\\n');\n}\n\n/**\n * Generate inline fragment spreads for all calendar types\n * Used in the main query to apply fragments to each calendar type\n */\nfunction generateInlineSpreads() {\n return CALENDARS.map(\n (cal) => `\n ... on ${cal}_Event {\n ...EventBasicFields_${cal}\n ...EventDateFields_${cal}\n ...EventContentFields_${cal}\n ...EventCategoryFields_${cal}\n }\n `,\n ).join('\\n');\n}\n\n/**\n * Generate inline fragment spreads for slider queries\n * Minimal fields for carousel displays\n */\nfunction generateSliderSpreads() {\n return CALENDARS.map(\n (cal) => `\n ... on ${cal}_Event {\n ...EventSliderFields_${cal}\n }\n `,\n ).join('\\n');\n}\n\n/**\n * Generate slider-specific fragments\n */\nfunction generateSliderFragments() {\n return CALENDARS.map(\n (cal) => `\n fragment EventSliderFields_${cal} on ${cal}_Event {\n title\n url\n image: commonAssetHeroImageSingle {\n url\n alt\n }\n startMonth: startDate @formatDateTime(format: \"M\")\n startDay: startDate @formatDateTime(format: \"d\")\n endMonth: endDate @formatDateTime(format: \"M\")\n endDay: endDate @formatDateTime(format: \"d\")\n }\n `,\n ).join('\\n');\n}\n\n/**\n * GraphQL queries for events\n */\nconst buildEventsCountQuery = (\n dateFilter: 'startsAfterOrAt' | 'rangeStart' = 'startsAfterOrAt',\n) => `\n query getEventsCount($startDate: String!, $related: [QueryArgument]) {\n count: solspace_calendar {\n events(relatedTo: $related, loadOccurrences: true, ${dateFilter}: $startDate) {\n ... on communications_Event {\n id\n }\n ... on submission_Event {\n id\n }\n }\n }\n }\n`;\n\nconst buildEventsQuery = (\n dateFilter: 'startsAfterOrAt' | 'rangeStart' = 'startsAfterOrAt',\n) => `\n query getEvents($startDate: String!, $related: [QueryArgument], $limit: Int, $offset: Int) {\n entries: solspace_calendar {\n events(\n relatedTo: $related\n loadOccurrences: true\n ${dateFilter}: $startDate\n limit: $limit\n offset: $offset\n ) {\n ${generateInlineSpreads()}\n }\n }\n }\n ${generateFragments()}\n`;\n\nexport const EVENTS_COUNT_QUERY = buildEventsCountQuery();\nexport const EVENTS_QUERY = buildEventsQuery();\n\n/**\n * Query for fetching category names by IDs\n * Queries the categories table directly\n */\nexport const CATEGORY_NAMES_QUERY = `\n query getCategoryNames($ids: [QueryArgument]!) {\n categories(id: $ids) {\n id\n title\n }\n }\n`;\n\n/**\n * Slider-specific query with minimal fields\n * Used by events slider for carousel displays\n */\nexport const EVENTS_SLIDER_QUERY = `\n query getEvents($startDate: String!, $related: [QueryArgument]) {\n entries: solspace_calendar {\n events(\n relatedTo: $related\n loadOccurrences: true\n startsAfterOrAt: $startDate\n limit: 12\n ) {\n ${generateSliderSpreads()}\n }\n }\n }\n ${generateSliderFragments()}\n`;\n\n/**\n * Events fetch strategy\n *\n * Fetches event data from the UMD Calendar GraphQL API.\n * Handles date filtering, category filtering, and pagination.\n *\n * @example\n * ```typescript\n * const feed = createBaseFeed({\n * token: 'my-token',\n * fetchStrategy: eventsFetchStrategy,\n * // ...\n * });\n * ```\n */\nconst baseFetchStrategy = createGraphQLFetchStrategy<EventEntry>({\n endpoint: 'https://calendar.umd.edu/graphql',\n\n queries: {\n entries: EVENTS_QUERY,\n count: EVENTS_COUNT_QUERY,\n },\n\n transformResponse: (data) => {\n if (!data || !data.data || !data.data.entries) {\n return null;\n }\n return data.data.entries.events || null;\n },\n\n transformCount: (data) => {\n if (!data || !data.data || !data.data.count) {\n return 0;\n }\n return data.data.count.events?.length || 0;\n },\n\n composeVariables: (baseVariables) => {\n const { categories, ...rest } = baseVariables;\n\n return {\n ...rest,\n related: categories,\n startDate: new Date().toDateString(),\n };\n },\n});\n\n/**\n * Fetch category names by their IDs\n */\nasync function fetchCategoryNames(\n categoryIds: string[],\n token?: string,\n): Promise<string[] | null> {\n try {\n const response = await fetchGraphQL({\n url: 'https://calendar.umd.edu/graphql',\n query: CATEGORY_NAMES_QUERY,\n token: token || '',\n variables: { ids: categoryIds },\n });\n\n if (!response || !response.data || !response.data.categories) {\n return null;\n }\n\n return response.data.categories.map((category: any) => category.title);\n } catch (error) {\n console.error('Fetch category names error:', error);\n return null;\n }\n}\n\nexport const eventsFetchStrategy = {\n ...baseFetchStrategy,\n fetchCategoryNames,\n};\n\n/**\n * Events fetch strategy for range queries\n *\n * Uses rangeStart filter instead of startsAfterOrAt.\n * Used for grouped event displays.\n */\nconst baseFetchStrategyRange = createGraphQLFetchStrategy<EventEntry>({\n endpoint: 'https://calendar.umd.edu/graphql',\n\n queries: {\n entries: buildEventsQuery('rangeStart'),\n count: buildEventsCountQuery('rangeStart'),\n },\n\n transformResponse: (data) => {\n if (!data || !data.data || !data.data.entries) {\n return null;\n }\n return data.data.entries.events || null;\n },\n\n transformCount: (data) => {\n if (!data || !data.data || !data.data.count) {\n return 0;\n }\n return data.data.count.events?.length || 0;\n },\n\n composeVariables: (baseVariables) => {\n const { categories, ...rest } = baseVariables;\n\n return {\n ...rest,\n related: categories,\n startDate: new Date().toDateString(),\n };\n },\n});\n\nexport const eventsFetchStrategyRange = {\n ...baseFetchStrategyRange,\n fetchCategoryNames,\n};\n"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ export interface ElementModel<T = HTMLElement> {
5
5
  }
6
6
  export interface Image {
7
7
  url: string;
8
+ alt?: string;
8
9
  altText?: string;
9
10
  id?: string;
10
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../source/types/core.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,WAAW;IAE3C,OAAO,EAAE,CAAC,CAAC;IAEX,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CACnC;AAOD,MAAM,WAAW,KAAK;IAEpB,GAAG,EAAE,MAAM,CAAC;IAEZ,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAOD,MAAM,WAAW,QAAQ;IAEvB,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;CACb;AAQD,MAAM,WAAW,SAAS;IAExB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAEpB,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;IAEZ,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,MAAM,WAAW,UAAW,SAAQ,SAAS;IAE3C,KAAK,EAAE,KAAK,EAAE,CAAC;CAChB;AAOD,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IAEjD,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAC;CACzB;AAOD,MAAM,WAAW,UAAW,SAAQ,SAAS;IAE3C,IAAI,EAAE,MAAM,CAAC;IAEb,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAEhD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAcD,MAAM,WAAW,YAAa,SAC5B,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,eAAe;CAAG"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../source/types/core.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,WAAW;IAE3C,OAAO,EAAE,CAAC,CAAC;IAEX,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CACnC;AAOD,MAAM,WAAW,KAAK;IAEpB,GAAG,EAAE,MAAM,CAAC;IAEZ,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAOD,MAAM,WAAW,QAAQ;IAEvB,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;CACb;AAQD,MAAM,WAAW,SAAS;IAExB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAEpB,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;IAEZ,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,MAAM,WAAW,UAAW,SAAQ,SAAS;IAE3C,KAAK,EAAE,KAAK,EAAE,CAAC;CAChB;AAOD,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IAEjD,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAC;CACzB;AAOD,MAAM,WAAW,UAAW,SAAQ,SAAS;IAE3C,IAAI,EAAE,MAAM,CAAC;IAEb,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAEhD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAcD,MAAM,WAAW,YAAa,SAC5B,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,eAAe;CAAG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@universityofmaryland/web-feeds-library",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "UMD Feed Elements",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -47,22 +47,13 @@
47
47
  "access": "public",
48
48
  "registry": "https://registry.npmjs.org/"
49
49
  },
50
- "scripts": {
51
- "clean": "rm -rf build",
52
- "docs": "npx typedoc --options ./typedoc.json",
53
- "prebuild": "node ../../scripts/update-badges.js",
54
- "release": "npm run test && npm run build && npm publish --access public",
55
- "build": "vite build",
56
- "dev": "vite build --watch",
57
- "start": "npm run dev",
58
- "test": "jest",
59
- "test:watch": "jest --watch",
60
- "test:coverage": "jest --coverage",
61
- "test:snapshot": "jest --updateSnapshot"
62
- },
63
50
  "dependencies": {
64
51
  "@types/postcss-js": "^4.0.4",
65
- "@universityofmaryland/web-token-library": "^1.0.0"
52
+ "@universityofmaryland/web-builder-library": "^1.0.3",
53
+ "@universityofmaryland/web-elements-library": "^1.6.10",
54
+ "@universityofmaryland/web-token-library": "^1.0.1",
55
+ "@universityofmaryland/web-utilities-library": "^1.0.5",
56
+ "@universityofmaryland/web-styles-library": "^1.8.6"
66
57
  },
67
58
  "peerDependencies": {
68
59
  "@universityofmaryland/web-builder-library": "^1.0.0",
@@ -71,9 +62,22 @@
71
62
  "@universityofmaryland/web-utilities-library": "^1.0.0"
72
63
  },
73
64
  "devDependencies": {
74
- "typescript": "^5.8.2",
65
+ "typescript": "~5.8.2",
75
66
  "vite": "^7.1.4",
76
67
  "vite-plugin-checker": "^0.10.3",
77
68
  "vite-plugin-dts": "^4.5.4"
69
+ },
70
+ "scripts": {
71
+ "clean": "rm -rf dist",
72
+ "docs": "npx typedoc --options ./typedoc.json",
73
+ "prebuild": "node ../../scripts/update-badges.js",
74
+ "release": "pnpm run test && pnpm run build && pnpm publish --access public",
75
+ "build": "vite build",
76
+ "dev": "vite build --watch",
77
+ "start": "pnpm run dev",
78
+ "test": "jest",
79
+ "test:watch": "jest --watch",
80
+ "test:coverage": "jest --coverage",
81
+ "test:snapshot": "jest --updateSnapshot"
78
82
  }
79
- }
83
+ }