diff --git a/.changeset/date-input-segment-lookup.md b/.changeset/date-input-segment-lookup.md new file mode 100644 index 0000000000..dca30179e0 --- /dev/null +++ b/.changeset/date-input-segment-lookup.md @@ -0,0 +1,7 @@ +--- +"@ark-ui/solid": patch +"@ark-ui/vue": patch +--- + +Fixed `DateInput.Segment` resolving segments by `type`, so segments sharing a type all rendered the first match's text. +Literal separators like `:` and `,` rendered as `/`. diff --git a/.changeset/vue-aschild-duplicate-class.md b/.changeset/vue-aschild-duplicate-class.md new file mode 100644 index 0000000000..c386fef2f0 --- /dev/null +++ b/.changeset/vue-aschild-duplicate-class.md @@ -0,0 +1,8 @@ +--- +"@ark-ui/vue": patch +--- + +- Fixed `asChild` duplicating the child element's `class`, so `` + rendered `class="child parent child"` instead of `class="parent child"`. +- Fixed `asChild` applying props to a leading comment node, which silently dropped them when a comment or a false `v-if` + preceded the child element. diff --git a/packages/solid/src/components/date-input/date-input-segment-context.tsx b/packages/solid/src/components/date-input/date-input-segment-context.tsx index ad2c9db9f6..500852bc06 100644 --- a/packages/solid/src/components/date-input/date-input-segment-context.tsx +++ b/packages/solid/src/components/date-input/date-input-segment-context.tsx @@ -10,5 +10,9 @@ export interface DateInputSegmentContextProps { export const DateInputSegmentContext = (props: DateInputSegmentContextProps) => { const api = useDateInputContext() const segmentGroupProps = useDateInputSegmentGroupPropsContext() - return {(segment) => props.children(segment())} + return ( + + {(segment, index) => props.children({ ...segment(), index } as DateSegment)} + + ) } diff --git a/packages/solid/src/components/date-input/date-input-segment.tsx b/packages/solid/src/components/date-input/date-input-segment.tsx index 8024011f24..993edc05fc 100644 --- a/packages/solid/src/components/date-input/date-input-segment.tsx +++ b/packages/solid/src/components/date-input/date-input-segment.tsx @@ -11,14 +11,18 @@ export interface DateInputSegmentProps extends HTMLProps<'span'>, DateInputSegme const splitSegmentProps = createSplitProps>() +type IndexedSegment = SegmentProps['segment'] & { index?: number } + export const DateInputSegment = (props: DateInputSegmentProps) => { const [segmentProps, localProps] = splitSegmentProps(props, ['segment']) const segmentGroupProps = useDateInputSegmentGroupPropsContext() const api = useDateInputContext() + // `type` alone doesn't identify a segment, since multiple segments can share it (e.g. `literal`) const currentSegment = createMemo(() => { + const index = (segmentProps.segment as IndexedSegment).index const segments = api().getSegments(segmentGroupProps) - return segments.find((s) => s.type === segmentProps.segment.type) ?? segmentProps.segment + return (typeof index === 'number' ? segments[index] : undefined) ?? segmentProps.segment }) const mergedProps = mergeProps( diff --git a/packages/solid/src/components/date-input/tests/date-input.test.tsx b/packages/solid/src/components/date-input/tests/date-input.test.tsx index b77d604d69..a10826b5e0 100644 --- a/packages/solid/src/components/date-input/tests/date-input.test.tsx +++ b/packages/solid/src/components/date-input/tests/date-input.test.tsx @@ -70,6 +70,20 @@ describe('Date Input', () => { expect(document.querySelector('[data-type="timeZoneName"]')).toBeInTheDocument() }) + it('should render each literal segment with its own text, not the first literal', () => { + render(() => ( + + )) + const literalSegments = document.querySelectorAll('[data-type="literal"]') + const literalTexts = Array.from(literalSegments).map((segment) => segment.textContent) + expect(literalTexts.length).toBeGreaterThan(1) + expect(literalTexts).toContain('/') + expect(literalTexts).toContain(':') + }) + it('should hide timeZoneName segment when hideTimeZone is true', () => { render(() => ( dateInput.value.getSegments(segmentGroupProps!.value).map((segment, index) => ({ ...segment, + index, key: `${segment.type}-${index}`, })), ) diff --git a/packages/vue/src/components/date-input/date-input-segment.vue b/packages/vue/src/components/date-input/date-input-segment.vue index 28700951c7..1dba954d52 100644 --- a/packages/vue/src/components/date-input/date-input-segment.vue +++ b/packages/vue/src/components/date-input/date-input-segment.vue @@ -26,9 +26,13 @@ const dateInput = useDateInputContext() useForwardExpose() +type IndexedSegment = SegmentProps['segment'] & { index?: number } + const currentSegment = computed(() => { const segments = dateInput.value.getSegments(segmentGroupProps!.value) - return segments.find((s) => s.type === props.segment.type) ?? props.segment + // `type` alone doesn't identify a segment, since multiple segments can share it (e.g. `literal`) + const index = (props.segment as IndexedSegment).index + return (typeof index === 'number' ? segments[index] : undefined) ?? props.segment }) const mergedProps = computed(() => diff --git a/packages/vue/src/components/date-input/tests/date-input.test.ts b/packages/vue/src/components/date-input/tests/date-input.test.ts index 02b1cd6653..0192327094 100644 --- a/packages/vue/src/components/date-input/tests/date-input.test.ts +++ b/packages/vue/src/components/date-input/tests/date-input.test.ts @@ -70,6 +70,20 @@ describe('Date Input', () => { expect(document.querySelector('[data-type="timeZoneName"]')).toBeInTheDocument() }) + it('should render each literal segment with its own text, not the first literal', () => { + render(ComponentUnderTest, { + props: { + defaultValue: [parseZonedDateTime('2025-02-03T08:45:00[America/Los_Angeles]')], + granularity: 'minute', + }, + }) + const literalSegments = document.querySelectorAll('[data-type="literal"]') + const literalTexts = Array.from(literalSegments).map((segment) => segment.textContent) + expect(literalTexts.length).toBeGreaterThan(1) + expect(literalTexts).toContain('/') + expect(literalTexts).toContain(':') + }) + it('should hide timeZoneName segment when hideTimeZone is true', () => { render(ComponentUnderTest, { props: { diff --git a/packages/vue/src/components/factory.test.tsx b/packages/vue/src/components/factory.test.tsx index 693336d9ba..6bed67f45b 100644 --- a/packages/vue/src/components/factory.test.tsx +++ b/packages/vue/src/components/factory.test.tsx @@ -1,5 +1,6 @@ import user from '@testing-library/user-event' import { render, screen } from '@testing-library/vue' +import { createCommentVNode, defineComponent, h, nextTick, ref } from 'vue' import { ark } from './factory.ts' const ComponentUnderTest = ( @@ -33,6 +34,75 @@ describe('Factory', () => { expect(screen.getByText('Ark UI')).toBeVisible() }) + it('should not duplicate the class of a plain child element', () => { + render( + + + Ark UI + + , + ) + const child = screen.getByTestId('child') + expect(child.className.split(/\s+/).filter(Boolean).sort()).toEqual(['child', 'parent']) + }) + + it('should call each handler of a plain child element once', async () => { + const onClickParent = vi.fn() + const onClickChild = vi.fn() + render( + +