@asteroidcms/core-utils 0.1.8 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.cts CHANGED
@@ -383,117 +383,8 @@ declare function Seo({ title, description, url, siteName, keywords, twitter, ima
383
383
  /** schema.org Article subtypes; pick per content kind. */
384
384
  type ArticleJsonLdType = "Article" | "BlogPosting" | "NewsArticle" | "TechArticle" | (string & {});
385
385
 
386
- interface AsteroidArticlePagePost {
387
- slug: string;
388
- title: string;
389
- description?: string;
390
- content?: string;
391
- featured_image?: string;
392
- tags?: string;
393
- published_date?: string | null;
394
- category?: {
395
- slug: string;
396
- name: string;
397
- };
398
- author?: {
399
- name: string;
400
- bio?: string;
401
- };
402
- }
403
- type AsteroidArticlePageUseArticleResult<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost> = {
404
- data?: TPost | null;
405
- loading: boolean;
406
- error?: unknown;
407
- };
408
- type AsteroidArticlePageUseArticle<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost> = (slug: string) => AsteroidArticlePageUseArticleResult<TPost>;
409
- interface AsteroidArticlePageProps<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost> {
410
- /** Article post slug to fetch. */
411
- slug: string;
412
- /** Inject the app-specific CMS fetching hook. */
413
- useArticle: AsteroidArticlePageUseArticle<TPost>;
414
- /** Site SEO config - enables client-side meta tags in React and Next.js apps. */
415
- seo?: AsteroidSeoConfig;
416
- /** schema.org Article subtype for the built-in JSON-LD. Default: "Article". */
417
- articleType?: ArticleJsonLdType;
418
- /** Mark this page as `noindex` for search engines. */
419
- noindex?: boolean;
420
- /** "Back to article" link. */
421
- backLink?: ReactNode;
422
- /** Wrap the entire component output. */
423
- renderRoot?: (params: {
424
- children: ReactNode;
425
- }) => ReactNode;
426
- /** Loading skeleton placeholder. */
427
- renderSkeleton?: () => ReactNode;
428
- /** Error or not-found state. */
429
- renderError?: (params: {
430
- error?: unknown;
431
- reason: "error" | "not-found";
432
- }) => ReactNode;
433
- /** Article post title / heading area. */
434
- renderHeader?: (params: {
435
- post: TPost;
436
- }) => ReactNode;
437
- /** Article post metadata line (date, author, reading time, category chip). */
438
- renderMeta?: (params: {
439
- post: TPost;
440
- }) => ReactNode;
441
- /** Article post description paragraph. */
442
- renderDescription?: (params: {
443
- post: TPost;
444
- }) => ReactNode;
445
- /** Featured / hero image. */
446
- renderFeaturedImage?: (params: {
447
- post: TPost;
448
- }) => ReactNode;
449
- /** Table of contents section. */
450
- renderToc?: (params: {
451
- post: TPost;
452
- }) => ReactNode;
453
- /** Main rich-text content body. */
454
- renderContent?: (params: {
455
- post: TPost;
456
- }) => ReactNode;
457
- /** Top advertisement slot. */
458
- renderPreArticle?: (params: {
459
- post: TPost;
460
- }) => ReactNode;
461
- /** Middle advertisement slot. */
462
- renderMidArticle?: (params: {
463
- post: TPost;
464
- }) => ReactNode;
465
- /** Bottom advertisement slot. */
466
- renderPostArticle?: (params: {
467
- post: TPost;
468
- }) => ReactNode;
469
- /** Tags section. */
470
- renderTags?: (params: {
471
- post: TPost;
472
- }) => ReactNode;
473
- /** Author details section. */
474
- renderAuthorDetails?: (params: {
475
- post: TPost;
476
- }) => ReactNode;
477
- /** Related / similar posts section. */
478
- renderRelatedPosts?: (params: {
479
- post: TPost;
480
- }) => ReactNode;
481
- /** Call-to-action section. */
482
- renderCTA?: (params: {
483
- post: TPost;
484
- }) => ReactNode;
485
- /** Override the built-in Article JSON-LD structured data. */
486
- renderJsonLd?: (params: {
487
- post: TPost;
488
- }) => ReactNode;
489
- /**
490
- * Escape hatch - receive the full result and render everything yourself.
491
- * When provided, structural render props above are ignored.
492
- */
493
- children?: (state: AsteroidArticlePageUseArticleResult<TPost>) => ReactNode;
494
- }
495
- declare function AsteroidArticlePage<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost>(props: AsteroidArticlePageProps<TPost>): string | number | boolean | Iterable<ReactNode> | react_jsx_runtime.JSX.Element | null | undefined;
496
-
386
+ /** Resolves a CMS asset id (or passthrough URL) to an absolute media URL. */
387
+ type ArticleImageResolver = (idOrUrl?: string) => string;
497
388
  /** Minimal article post shape shared across Asteroid CMS apps. */
498
389
  interface AsteroidArticlePost {
499
390
  slug: string;
@@ -513,18 +404,24 @@ interface AsteroidArticleCategoryGroup<TPost extends AsteroidArticlePost = Aster
513
404
  categorySlug: string;
514
405
  posts: TPost[];
515
406
  }
516
- interface AsteroidArticlesUsePostsResult<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
407
+ /** Runtime-agnostic computed state shared by client + server listing. */
408
+ interface ArticlesViewState<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
517
409
  posts: TPost[];
518
- featured?: TPost | null;
410
+ featured: TPost | null;
519
411
  rest: TPost[];
520
- loading: boolean;
521
- error?: unknown;
412
+ categoryGroups: AsteroidArticleCategoryGroup<TPost>[];
413
+ isEmpty: boolean;
414
+ isSearching: boolean;
415
+ /** The effective (already-debounced, for client) search query. */
416
+ searchQuery: string;
522
417
  }
523
- type AsteroidArticlesUsePosts<TPost extends AsteroidArticlePost = AsteroidArticlePost> = (searchQuery: string) => AsteroidArticlesUsePostsResult<TPost>;
418
+ type AsteroidArticlesEmptyReason = "error" | "no-posts" | "no-results";
524
419
  interface AsteroidArticlesSearchParams {
525
420
  value: string;
526
421
  onChange: (value: string) => void;
527
- onSubmit: (event: React.FormEvent) => void;
422
+ onSubmit: (event: {
423
+ preventDefault: () => void;
424
+ }) => void;
528
425
  }
529
426
  interface AsteroidArticlesHeaderParams {
530
427
  eyebrow: ReactNode;
@@ -534,16 +431,17 @@ interface AsteroidArticlesHeaderParams {
534
431
  }
535
432
  interface AsteroidArticlesFeaturedCardParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
536
433
  post: TPost;
434
+ cmsImage: ArticleImageResolver;
537
435
  }
538
436
  interface AsteroidArticlesPostCardParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
539
437
  post: TPost;
540
438
  index: number;
541
439
  group: AsteroidArticleCategoryGroup<TPost>;
440
+ cmsImage: ArticleImageResolver;
542
441
  }
543
442
  interface AsteroidArticlesPostGridParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
544
443
  posts: TPost[];
545
444
  group: AsteroidArticleCategoryGroup<TPost>;
546
- /** Pre-built post cards - render as-is or wrap. */
547
445
  children: ReactNode;
548
446
  }
549
447
  interface AsteroidArticlesCategoryHeadingParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
@@ -551,10 +449,8 @@ interface AsteroidArticlesCategoryHeadingParams<TPost extends AsteroidArticlePos
551
449
  }
552
450
  interface AsteroidArticlesCategoryGroupParams<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
553
451
  group: AsteroidArticleCategoryGroup<TPost>;
554
- /** Default heading + grid - use it, wrap it, or replace entirely. */
555
452
  defaultContent: ReactNode;
556
453
  }
557
- type AsteroidArticlesEmptyReason = "error" | "no-posts" | "no-results";
558
454
  interface AsteroidArticlesEmptyParams {
559
455
  reason: AsteroidArticlesEmptyReason;
560
456
  searchQuery?: string;
@@ -565,77 +461,145 @@ interface AsteroidArticlesContentParams {
565
461
  groups: ReactNode;
566
462
  noSearchResults: ReactNode;
567
463
  }
568
- interface AsteroidArticlesState<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
569
- posts: TPost[];
570
- featured: TPost | null | undefined;
571
- rest: TPost[];
572
- categoryGroups: AsteroidArticleCategoryGroup<TPost>[];
573
- loading: boolean;
574
- error: unknown;
575
- hasError: boolean;
576
- isEmpty: boolean;
577
- isSearching: boolean;
578
- searchQuery: string;
579
- debouncedSearchQuery: string;
580
- setSearchQuery: (value: string) => void;
464
+
465
+ interface AsteroidArticlePagePost {
466
+ slug: string;
467
+ title: string;
468
+ description?: string;
469
+ content?: string;
470
+ featured_image?: string;
471
+ tags?: string;
472
+ published_date?: string | null;
473
+ category?: {
474
+ slug: string;
475
+ name: string;
476
+ };
477
+ author?: {
478
+ name: string;
479
+ bio?: string;
480
+ };
581
481
  }
582
- interface AsteroidArticlesProps<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
583
- /** Inject app-specific CMS fetching. Receives the debounced search query. */
584
- usePosts: AsteroidArticlesUsePosts<TPost>;
585
- /** Limit listing to a single category slug. */
586
- categorySlug?: string;
587
- /** Limit listing to a single post slug. */
588
- articleSlug?: string;
589
- /** Debounce delay for search in ms. Default: 800 */
590
- searchDebounceMs?: number;
591
- /** Site SEO config - enables client-side meta tags in React and Next.js apps. */
482
+ type Slot<TPost> = (params: {
483
+ post: TPost;
484
+ cmsImage: ArticleImageResolver;
485
+ }) => ReactNode;
486
+ interface ArticleBodyRenderProps<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost> {
487
+ backLink?: ReactNode;
488
+ renderHeader?: Slot<TPost>;
489
+ renderMeta?: Slot<TPost>;
490
+ renderDescription?: Slot<TPost>;
491
+ renderFeaturedImage?: Slot<TPost>;
492
+ renderToc?: Slot<TPost>;
493
+ renderContent?: Slot<TPost>;
494
+ renderPreArticle?: Slot<TPost>;
495
+ renderMidArticle?: Slot<TPost>;
496
+ renderPostArticle?: Slot<TPost>;
497
+ renderTags?: Slot<TPost>;
498
+ renderAuthorDetails?: Slot<TPost>;
499
+ renderRelatedPosts?: (params: {
500
+ post: TPost;
501
+ relatedPosts: TPost[];
502
+ cmsImage: ArticleImageResolver;
503
+ }) => ReactNode;
504
+ renderCTA?: Slot<TPost>;
505
+ }
506
+
507
+ type AsteroidArticlePageUseArticleResult<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost> = {
508
+ data?: TPost | null;
509
+ loading: boolean;
510
+ error?: unknown;
511
+ };
512
+ type AsteroidArticlePageUseArticle<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost> = (slug: string) => AsteroidArticlePageUseArticleResult<TPost>;
513
+ interface AsteroidArticlePageProps<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost> extends ArticleBodyRenderProps<TPost> {
514
+ slug: string;
515
+ useArticle: AsteroidArticlePageUseArticle<TPost>;
592
516
  seo?: AsteroidSeoConfig;
593
- /** Mark this page as `noindex` for search engines. */
517
+ articleType?: ArticleJsonLdType;
594
518
  noindex?: boolean;
519
+ relatedPosts?: TPost[];
520
+ renderRoot?: (params: {
521
+ children: ReactNode;
522
+ }) => ReactNode;
523
+ renderSkeleton?: () => ReactNode;
524
+ renderError?: (params: {
525
+ error?: unknown;
526
+ reason: "error" | "not-found";
527
+ }) => ReactNode;
528
+ renderJsonLd?: (params: {
529
+ post: TPost;
530
+ }) => ReactNode;
531
+ children?: (state: AsteroidArticlePageUseArticleResult<TPost>) => ReactNode;
532
+ }
533
+ declare function AsteroidArticlePage<TPost extends AsteroidArticlePagePost = AsteroidArticlePagePost>(props: AsteroidArticlePageProps<TPost>): react_jsx_runtime.JSX.Element;
534
+
535
+ interface ArticlesListingRenderProps<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
595
536
  eyebrow?: ReactNode;
596
537
  title?: ReactNode;
597
538
  description?: ReactNode;
598
- /** Wrap the entire component output. */
599
539
  renderRoot?: (params: {
600
540
  children: ReactNode;
601
541
  }) => ReactNode;
602
- /** Replace the default header layout (eyebrow + title + description + search). */
603
542
  renderHeader?: (params: AsteroidArticlesHeaderParams) => ReactNode;
604
- /** Replace the search control. */
605
- renderSearch?: (params: AsteroidArticlesSearchParams) => ReactNode;
606
- /** Featured hero card. Return `null` to suppress. Hidden while searching. */
607
543
  renderFeaturedCard?: (params: AsteroidArticlesFeaturedCardParams<TPost>) => ReactNode;
608
- /** Individual post card inside a grid. */
609
544
  renderPostCard: (params: AsteroidArticlesPostCardParams<TPost>) => ReactNode;
610
- /** Category section heading. */
611
545
  renderCategoryHeading?: (params: AsteroidArticlesCategoryHeadingParams<TPost>) => ReactNode;
612
- /** Grid wrapper for post cards within a category. */
613
546
  renderPostGrid?: (params: AsteroidArticlesPostGridParams<TPost>) => ReactNode;
614
- /**
615
- * Full category section (heading + grid).
616
- * Receives `defaultContent` so you can wrap rather than fully replace.
617
- */
618
547
  renderCategoryGroup?: (params: AsteroidArticlesCategoryGroupParams<TPost>) => ReactNode;
619
- /** Loading state. */
620
548
  renderSkeleton?: () => ReactNode;
621
- /** Error, empty feed, or zero search results. Return `null` to suppress. */
622
549
  renderEmpty?: (params: AsteroidArticlesEmptyParams) => ReactNode;
623
- /** Wrap featured card + category groups. */
624
550
  renderContent?: (params: AsteroidArticlesContentParams) => ReactNode;
625
- /** Override the built-in CollectionPage JSON-LD structured data. */
626
- renderJsonLd?: (params: AsteroidArticlesState<TPost>) => ReactNode;
627
- /**
628
- * Escape hatch - receive full computed state and render everything yourself.
629
- * When provided, structural render props above are ignored (slot render props
630
- * like `renderPostCard` are still used if you call them manually).
631
- */
632
- children?: (state: AsteroidArticlesState<TPost>) => ReactNode;
633
- /** Custom category grouping. */
551
+ }
552
+
553
+ interface AsteroidArticlesUsePostsResult<TPost extends AsteroidArticlePost = AsteroidArticlePost> {
554
+ posts: TPost[];
555
+ featured?: TPost | null;
556
+ rest?: TPost[];
557
+ loading: boolean;
558
+ error?: unknown;
559
+ }
560
+ type AsteroidArticlesUsePosts<TPost extends AsteroidArticlePost = AsteroidArticlePost> = (searchQuery: string) => AsteroidArticlesUsePostsResult<TPost>;
561
+ interface AsteroidArticlesState<TPost extends AsteroidArticlePost = AsteroidArticlePost> extends ArticlesViewState<TPost> {
562
+ loading: boolean;
563
+ error: unknown;
564
+ hasError: boolean;
565
+ /** Raw, un-debounced input value. Bind your search box to this. */
566
+ inputValue: string;
567
+ setSearchQuery: (value: string) => void;
568
+ }
569
+ interface AsteroidArticlesProps<TPost extends AsteroidArticlePost = AsteroidArticlePost> extends ArticlesListingRenderProps<TPost> {
570
+ usePosts: AsteroidArticlesUsePosts<TPost>;
571
+ categorySlug?: string;
572
+ articleSlug?: string;
573
+ searchDebounceMs?: number;
574
+ seo?: AsteroidSeoConfig;
575
+ noindex?: boolean;
576
+ renderSearch?: (params: AsteroidArticlesSearchParams) => ReactNode;
577
+ renderJsonLd?: (state: AsteroidArticlesState<TPost>) => ReactNode;
634
578
  groupPostsByCategory?: (posts: TPost[]) => AsteroidArticleCategoryGroup<TPost>[];
579
+ children?: (state: AsteroidArticlesState<TPost>) => ReactNode;
635
580
  }
581
+ declare function useAsteroidArticlesState<TPost extends AsteroidArticlePost = AsteroidArticlePost>(props: Pick<AsteroidArticlesProps<TPost>, "usePosts" | "categorySlug" | "articleSlug" | "searchDebounceMs" | "groupPostsByCategory">): AsteroidArticlesState<TPost>;
582
+ declare function AsteroidArticlesListing<TPost extends AsteroidArticlePost = AsteroidArticlePost>(props: AsteroidArticlesProps<TPost>): react_jsx_runtime.JSX.Element;
583
+
636
584
  declare function defaultGetCategoryName(post: Pick<AsteroidArticlePost, "category">): string | undefined;
637
585
  declare function defaultGroupPostsByCategory<TPost extends AsteroidArticlePost = AsteroidArticlePost>(posts: TPost[]): AsteroidArticleCategoryGroup<TPost>[];
638
- declare function useAsteroidArticlesState<TPost extends AsteroidArticlePost = AsteroidArticlePost>({ usePosts, categorySlug, articleSlug, searchDebounceMs, groupPostsByCategory, }: Pick<AsteroidArticlesProps<TPost>, "usePosts" | "categorySlug" | "articleSlug" | "searchDebounceMs" | "groupPostsByCategory">): AsteroidArticlesState<TPost>;
639
- declare function AsteroidArticlesListing<TPost extends AsteroidArticlePost = AsteroidArticlePost>(props: AsteroidArticlesProps<TPost>): string | number | boolean | Iterable<ReactNode> | react_jsx_runtime.JSX.Element | null | undefined;
640
586
 
641
- export { type AsteroidArticleCategoryGroup, AsteroidArticlePage, type AsteroidArticlePagePost, type AsteroidArticlePageProps, type AsteroidArticlePageUseArticle, type AsteroidArticlePageUseArticleResult, type AsteroidArticlePost, AsteroidArticlesListing, type AsteroidArticlesProps, type AsteroidArticlesState, type AsteroidArticlesUsePosts, type AsteroidArticlesUsePostsResult, AsteroidCMSProvider, type AsteroidCMSProviderProps, type ExtractHeadingsOptions, type ExtractedHeading, type HeadingLevel, JsonLd, RichTextContent, Seo, type UseCmsContentOptions, type UseCmsMutateOptions, defaultGetCategoryName, defaultGroupPostsByCategory, extractHeadingsFromElement, extractHeadingsFromHtml, slugify, useAsteroidArticlesState, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
587
+ interface ArticleSearchBoxProps {
588
+ /** URL query param the search writes to. Default: "q". */
589
+ paramKey?: string;
590
+ placeholder?: string;
591
+ /** Debounce before navigating. Default: 500ms. */
592
+ debounceMs?: number;
593
+ className?: string;
594
+ /** Override the default input UI. */
595
+ render?: (params: {
596
+ value: string;
597
+ onChange: (value: string) => void;
598
+ onSubmit: (event: {
599
+ preventDefault: () => void;
600
+ }) => void;
601
+ }) => ReactNode;
602
+ }
603
+ declare function ArticleSearchBox({ paramKey, placeholder, debounceMs, className, render, }: ArticleSearchBoxProps): react_jsx_runtime.JSX.Element;
604
+
605
+ export { type ArticleBodyRenderProps, type ArticleImageResolver, ArticleSearchBox, type ArticleSearchBoxProps, type AsteroidArticleCategoryGroup, AsteroidArticlePage, type AsteroidArticlePagePost, type AsteroidArticlePageProps, type AsteroidArticlePageUseArticle, type AsteroidArticlePageUseArticleResult, type AsteroidArticlePost, type AsteroidArticlesCategoryGroupParams, type AsteroidArticlesCategoryHeadingParams, type AsteroidArticlesContentParams, type AsteroidArticlesEmptyParams, type AsteroidArticlesEmptyReason, type AsteroidArticlesFeaturedCardParams, type AsteroidArticlesHeaderParams, AsteroidArticlesListing, type AsteroidArticlesPostCardParams, type AsteroidArticlesPostGridParams, type AsteroidArticlesProps, type AsteroidArticlesSearchParams, type AsteroidArticlesState, type AsteroidArticlesUsePosts, type AsteroidArticlesUsePostsResult, AsteroidCMSProvider, type AsteroidCMSProviderProps, type ExtractHeadingsOptions, type ExtractedHeading, type HeadingLevel, JsonLd, RichTextContent, Seo, type UseCmsContentOptions, type UseCmsMutateOptions, defaultGetCategoryName, defaultGroupPostsByCategory, extractHeadingsFromElement, extractHeadingsFromHtml, slugify, useAsteroidArticlesState, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };