Unlike other multi-value props, date and link transforms keep empty rows, causing failures
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3587494. -->
Reported by: [tedbow](https://www.drupal.org/user/240860)
Related to !1002
>>>
<h3 id="overview">Overview</h3>
<p>For date and link props that are multi-value with fixed cardinality (not unlimited), empty rows between filled values are sent to the backend as <code>null</code>, causing the component to fail to render. For example, if a date prop with cardinality 3 has values in rows 0 and 2 but row 1 is empty, the model sent to the backend is <code>["2026-04-09", null, "2026-04-11"]</code> instead of <code>["2026-04-09", "2026-04-11"]</code>. This is the root cause of the "random dates on reload" problem described in <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3582883" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3582883</a></span>, which may be a duplicate of this issue.</p>
<p>This only affects date and link props. Other prop types like string and number correctly strip empty rows before sending data to the backend.</p>
<p>Date and link props fail differently because the null value reaches different points in the backend pipeline:</p>
<p><strong>Date prop error:</strong> The null becomes a garbage date string <code>"Z"</code> (from JS parsing <code>new Date(" +0000")</code>). Since this is still a string, auto-save succeeds and the garbage value is persisted. The failure occurs later during component rendering when validation rejects the invalid date-time format. The preview shows the error inline, and on reload random dates populate the empty rows.<br><br>
<code>Drupal\Core\Render\Component\Exception\InvalidComponentException: [canvas:date4/prop[1]] Invalid date-time "Z", expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm.</code></p>
<p><strong>Link prop error:</strong> The null value hits <code>LinkUrl::getCastedValue()</code> which has a <code>string</code> return type but returns <code>null</code>. This causes a PHP <code>TypeError</code> during auto-save normalization in <code>AutoSaveManager::normalizeFieldValues()</code>, which has no try-catch for this case. The auto-save crashes before the value is persisted. The preview does not update, the error only appears in Drupal logs, and the change is lost on reload.<br><br>
<code>TypeError: Drupal\canvas\TypedData\LinkUrl::getCastedValue(): Return value must be of type string, null returned</code></p>
<p>Preserving null values in the array cannot work because the backend iterates every item in the value array and processes it as a real field value through the prop expression evaluation pipeline (<code>Evaluator::doEvaluate()</code>). Null items get evaluated as if they contain valid data — for dates this produces garbage strings, for links it violates PHP return type contracts. The backend has no concept of "positional placeholders" and no mechanism to skip null array entries.</p>
<p>The root cause is in <code>ui/src/utils/transforms.ts</code>. The <code>mainProperty</code> transform (used by string/number) filters all null and empty values with <code>.filter((v) =&gt; v !== null &amp;&amp; v !== '')</code>. The <code>link</code> and <code>dateTime</code> transforms instead used a <code>trimNulls()</code> function that only removed leading and trailing nulls but preserved internal nulls as "positional placeholders."</p>
<p>The <code>trimNulls()</code> function was introduced in <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3581431" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3581431</a></span> as <code>trimTrailingNulls()</code>, then expanded in <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3581829" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3581829</a></span> to also strip leading nulls. The design choice to preserve internal nulls as "positional placeholders so the backend renders filled rows at the correct position" was an incorrect assumption — no backend requirement for this exists, and other prop types (string, number) have always stripped all empty values without issue.</p>
<h3 id="proposed-resolution">Proposed resolution</h3>
<p>Remove the <code>trimNulls()</code> function and replace its usage in the <code>link</code> and <code>dateTime</code> transforms with <code>.filter((v) => v !== null && v !== '')</code>, matching the behavior already used by the <code>mainProperty</code> transform for string and number props.</p>
<h3 id="ui-changes">User interface changes</h3>
<p>Multi-value date and link props with fixed cardinality now correctly strip empty rows from the data sent to the backend, preventing rendering errors when empty rows exist between filled values.</p>
issue