diff --git a/package.json b/package.json index 350c75b7da5..c795db2aefa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qwik-monorepo", - "version": "1.2.17", + "version": "1.2.18", "config": { "commitizen": { "path": "./node_modules/cz-conventional-changelog" diff --git a/packages/create-qwik/package.json b/packages/create-qwik/package.json index d5315974f61..62ae28e469e 100644 --- a/packages/create-qwik/package.json +++ b/packages/create-qwik/package.json @@ -1,7 +1,7 @@ { "name": "create-qwik", "description": "Interactive CLI for create Qwik projects and adding features.", - "version": "1.2.17", + "version": "1.2.18", "author": "Builder.io Team", "bin": "./create-qwik.cjs", "bugs": "https://github.com/BuilderIO/qwik/issues", diff --git a/packages/docs/src/components/on-this-page/on-this-page.tsx b/packages/docs/src/components/on-this-page/on-this-page.tsx index ecb9e5c3444..0677f9b984a 100644 --- a/packages/docs/src/components/on-this-page/on-this-page.tsx +++ b/packages/docs/src/components/on-this-page/on-this-page.tsx @@ -37,6 +37,7 @@ const QWIKCITY_GROUP = [ 'endpoints', 'env-variables', 'guides', + 'html-attributes', 'layout', 'middleware', 'pages', @@ -46,6 +47,7 @@ const QWIKCITY_GROUP = [ 'routing', 'server$', 'troubleshooting', + 'validator', ]; const QWIKCITY_ADVANCED_GROUP = [ 'content-security-policy', diff --git a/packages/docs/src/media/ecosystem/discord.svg b/packages/docs/src/media/ecosystem/discord.svg new file mode 100644 index 00000000000..bdfae6a93bb --- /dev/null +++ b/packages/docs/src/media/ecosystem/discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/docs/src/routes/api/qwik-city-middleware-request-handler/index.md b/packages/docs/src/routes/api/qwik-city-middleware-request-handler/index.md index 4629b930bee..f7638d9a9f9 100644 --- a/packages/docs/src/routes/api/qwik-city-middleware-request-handler/index.md +++ b/packages/docs/src/routes/api/qwik-city-middleware-request-handler/index.md @@ -261,7 +261,7 @@ export interface RequestEventBase | Property | Modifiers | Type | Description | | ----------------- | --------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [basePathname](#) | readonly | string | The base pathname of the request, which can be configured at build time. Defaults to /. | -| [cacheControl](#) | readonly | (cacheControl: [CacheControl](#cachecontrol), target?: CacheControlTarget) => void |

Convenience method to set the Cache-Control header. Depending on your CDN, you may want to add another cacheControl with the second argument set to CDN-Cache-Control or any other value (we provide the most common values for auto-complete, but you can use any string you want).

See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control and https://qwik.builder.io/docs/caching/#CDN-Cache-Controls for more information.

| +| [cacheControl](#) | readonly | (cacheControl: [CacheControl](#cachecontrol), target?: CacheControlTarget) => void |

Convenience method to set the Cache-Control header. Depending on your CDN, you may want to add another cacheControl with the second argument set to CDN-Cache-Control or any other value (we provide the most common values for auto-complete, but you can use any string you want).

See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control and https://qwik.builder.io/docs/caching/\#CDN-Cache-Controls for more information.

| | [clientConn](#) | readonly | [ClientConn](#clientconn) | Provides information about the client connection, such as the IP address and the country the request originated from. | | [cookie](#) | readonly | [Cookie](#cookie) |

HTTP request and response cookie. Use the get() method to retrieve a request cookie value. Use the set() method to set a response cookie value.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies

| | [env](#) | readonly | [EnvGetter](#envgetter) | Platform provided environment variables. | diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json index 1ac65209b0c..da891ed310a 100644 --- a/packages/docs/src/routes/api/qwik/api.json +++ b/packages/docs/src/routes/api/qwik/api.json @@ -1848,7 +1848,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikKeyboardEvent extends SyntheticEvent \n```\n**Extends:** SyntheticEvent<T, [NativeKeyboardEvent](#nativekeyboardevent)>\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [altKey](#) | | boolean | |\n| [charCode](#) | | number | |\n| [ctrlKey](#) | | boolean | |\n| [isComposing](#) | | boolean | |\n| [key](#) | | string | See the \\[DOM Level 3 Events spec\\](https://www.w3.org/TR/uievents-key/\\#named-key-attribute-values). for possible values |\n| [keyCode](#) | | number | |\n| [locale](#) | | string | |\n| [location](#) | | number | |\n| [metaKey](#) | | boolean | |\n| [repeat](#) | | boolean | |\n| [shiftKey](#) | | boolean | |\n| [which](#) | | number | |\n\n\n| Method | Description |\n| --- | --- |\n| [getModifierState(key)](#qwikkeyboardevent-getmodifierstate) | See \\[DOM Level 3 Events spec\\](https://www.w3.org/TR/uievents-key/\\#keys-modifier). for a list of valid (case-sensitive) arguments to this method. |", + "content": "```typescript\nexport interface QwikKeyboardEvent extends SyntheticEvent \n```\n**Extends:** SyntheticEvent<T, [NativeKeyboardEvent](#nativekeyboardevent)>\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [altKey](#) | | boolean | |\n| [charCode](#) | | number | |\n| [code](#) | | string | |\n| [ctrlKey](#) | | boolean | |\n| [isComposing](#) | | boolean | |\n| [key](#) | | string | See the \\[DOM Level 3 Events spec\\](https://www.w3.org/TR/uievents-key/\\#named-key-attribute-values). for possible values |\n| [keyCode](#) | | number | |\n| [locale](#) | | string | |\n| [location](#) | | number | |\n| [metaKey](#) | | boolean | |\n| [repeat](#) | | boolean | |\n| [shiftKey](#) | | boolean | |\n| [which](#) | | number | |\n\n\n| Method | Description |\n| --- | --- |\n| [getModifierState(key)](#qwikkeyboardevent-getmodifierstate) | See \\[DOM Level 3 Events spec\\](https://www.w3.org/TR/uievents-key/\\#keys-modifier). for a list of valid (case-sensitive) arguments to this method. |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-events.ts", "mdFile": "qwik.qwikkeyboardevent.md" }, diff --git a/packages/docs/src/routes/api/qwik/index.md b/packages/docs/src/routes/api/qwik/index.md index d1233fe2a2b..71674cd86fd 100644 --- a/packages/docs/src/routes/api/qwik/index.md +++ b/packages/docs/src/routes/api/qwik/index.md @@ -1924,7 +1924,7 @@ At times it is necessary to store values on a store that are non-serializable. N You can use `noSerialize()` to mark a value as non-serializable. The value is persisted in the Store but does not survive serialization. The implication is that when your application is resumed, the value of this object will be `undefined`. You will be responsible for recovering from this. -See: [noSerialize Tutorial](http://qwik.builder.io/tutorial/store/no-serialize) +See: [noSerialize Tutorial](https://qwik.builder.io/tutorial/store/no-serialize) ```typescript noSerialize: (input: T) => NoSerialize; @@ -2385,6 +2385,7 @@ export interface QwikKeyboardEvent extends SyntheticEvent { ### Caveats - The provided value will not be globally available across the whole render tree, but only to descendant components in the tree. +- If the context isn't used during SSR, it will not be serialized. So if you want to have the context available in the client even though you don't use it during SSR, you can call `useContext()` in the parent component to force it to be serialized. ## `useContext()` diff --git a/packages/docs/src/routes/docs/(qwik)/components/slots/index.mdx b/packages/docs/src/routes/docs/(qwik)/components/slots/index.mdx index 5d90b36d853..9becd79e047 100644 --- a/packages/docs/src/routes/docs/(qwik)/components/slots/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/components/slots/index.mdx @@ -235,3 +235,11 @@ The two approaches can best be described as declarative vs imperative. They both Qwik uses the declarative projection approach. The reason for this is that Qwik needs to be able to render parent/children components independently from each other. With an imperative (`children`) approach, the child component can modify the `children` in countless ways. If a child component relied on `children`, it would be forced to re-render whenever a parent component would re-render to reapply the imperative transformation to the `children`. The extra rendering goes explicitly against the goals of Qwik components rendering in isolation. > **Note**: Due to Slots being declarative in Qwik, projection with `` will only work if the parent component is wrapped in `component$()`. If parent component is not wrapped in `component$()`, it is considered an [inline component](/docs/components/overview/#inline-components) and `` will not work. + +### Advanced: Slots and Context + +Slotted components have access to the [Context](../context/) of their parent component, even while they aren't being projected. Furthermore, if the parent is projecting the `` inside another component, the slotted components will also have access to the Contexts of that deeper component. + +However, if a component hasn't been projected yet because the `` are rendered conditionally, it's impossible to know about the deeper Context, and then the slotted component will only see the Context of the immediate parent. + +As such, it's safest to avoid these situations; if you are providing Contexts, don't conditionally render your ``. diff --git a/packages/docs/src/routes/docs/(qwik)/components/tasks/index.mdx b/packages/docs/src/routes/docs/(qwik)/components/tasks/index.mdx index a0bc1e9c4d8..c73ef9c37f2 100644 --- a/packages/docs/src/routes/docs/(qwik)/components/tasks/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/components/tasks/index.mdx @@ -438,6 +438,20 @@ const Clock = component$<{ isRunning: Signal }>(({ isRunning }) => { > In this example the clock starts running immediately on the browser regardless of whether it is visible or not. +### Advanced: Time of running, and managing visibility with CSS + +Internally, `useVisibleTask$` is implemented by adding an attribute on the first rendered component (either the returned component or in case of a Fragment, its first child). With the standard `eagerness`, this means that if the first rendered component is hidden, the task will not run. + +This means that you can use CSS to influence when the task runs. For example, if the task should only run on a mobile device, you can return a `
` (in the case of Tailwind CSS). + +This also means you cannot unhide a component using a visible task; for that you can return a Fragment: + +```tsx +return (<> +
+
); }); + +suite('should properly render styles from style prop', () => { + const RenderJSX = component$(() => { + const pStyles = { + fontSize: 30, // auto-converted to px + fontWeight: 800, // shouldn't get converted to px + }; + return ( +
+
+

Big square

+
+
+ ); + }); + + test('SSR jsx style render', async () => { + const output = await renderToString(, { containerTagName: 'div' }); + const document = createDocument(); + document.body.innerHTML = output.html; + const main = document.querySelector('#root')!; + const resultHTML = `

Big square

`; + assert.equal(main.innerHTML, resultHTML); + }); + + test('CSR jsx style render', async () => { + const { screen, render } = await createDOM(); + + await render(); + const main = screen.querySelector('#root')!; + const resultHTML = `

Big square

`; + assert.equal(main.innerHTML, resultHTML); + }); +}); diff --git a/packages/qwik/src/core/render/execute-component.ts b/packages/qwik/src/core/render/execute-component.ts index 5501baf73ac..0779fe4633f 100644 --- a/packages/qwik/src/core/render/execute-component.ts +++ b/packages/qwik/src/core/render/execute-component.ts @@ -15,6 +15,7 @@ import { handleError } from './error-handling'; import { HOST_FLAG_DIRTY, HOST_FLAG_MOUNTED, type QContext } from '../state/context'; import { isSignal, SignalUnassignedException } from '../state/signal'; import { isJSXNode } from './jsx/jsx-runtime'; +import { isUnitlessNumber } from '../util/unitless_number'; export interface ExecuteComponentOutput { node: JSXNode | null; @@ -164,8 +165,11 @@ export const stringifyStyle = (obj: any): string => { if (Object.prototype.hasOwnProperty.call(obj, key)) { const value = obj[key]; if (value != null) { - const normalizedKey = key.startsWith('--') ? key : fromCamelToKebabCase(key); - chunks.push(normalizedKey + ':' + value); + if (key.startsWith('--')) { + chunks.push(key + ':' + value); + } else { + chunks.push(fromCamelToKebabCase(key) + ':' + setValueForStyle(key, value)); + } } } } @@ -175,6 +179,13 @@ export const stringifyStyle = (obj: any): string => { return String(obj); }; +const setValueForStyle = (styleName: string, value: any) => { + if (typeof value === 'number' && value !== 0 && !isUnitlessNumber(styleName)) { + return value + 'px'; + } + return value; +}; + export const getNextIndex = (ctx: RenderContext) => { return intToStr(ctx.$static$.$containerState$.$elementIndex$++); }; diff --git a/packages/qwik/src/core/render/jsx/types/jsx-qwik-events.ts b/packages/qwik/src/core/render/jsx/types/jsx-qwik-events.ts index e643b00bd89..4dbf86931fe 100644 --- a/packages/qwik/src/core/render/jsx/types/jsx-qwik-events.ts +++ b/packages/qwik/src/core/render/jsx/types/jsx-qwik-events.ts @@ -100,6 +100,7 @@ export interface QwikChangeEvent extends SyntheticEvent { export interface QwikKeyboardEvent extends SyntheticEvent { isComposing: boolean; altKey: boolean; + /** @deprecated Deprecated. */ charCode: number; ctrlKey: boolean; /** @@ -112,12 +113,15 @@ export interface QwikKeyboardEvent extends SyntheticEvent { + return unitlessNumbers.has(name); +}; diff --git a/packages/qwik/src/optimizer/src/plugins/plugin.ts b/packages/qwik/src/optimizer/src/plugins/plugin.ts index bfa48a6ce22..9f09e6b216c 100644 --- a/packages/qwik/src/optimizer/src/plugins/plugin.ts +++ b/packages/qwik/src/optimizer/src/plugins/plugin.ts @@ -1,5 +1,7 @@ -import { createOptimizer } from '../optimizer'; +import type { Rollup } from 'vite'; +import { hashCode } from '../../../core/util/hash_code'; import { generateManifestFromBundles, getValidManifest } from '../manifest'; +import { createOptimizer } from '../optimizer'; import type { Diagnostic, EntryStrategy, @@ -17,8 +19,6 @@ import type { TransformOutput, } from '../types'; import { createLinter, type QwikLinter } from './eslint-plugin'; -import type { Rollup } from 'vite'; -import { hashCode } from '../../../core/util/hash_code'; const REG_CTX_NAME = ['server']; @@ -445,10 +445,10 @@ export function createPlugin(optimizerOptions: OptimizerOptions = {}) { const transformedOutput = isSSR ? ssrTransformedOutputs.get(importer) : transformedOutputs.get(importer); - const p = transformedOutput?.[0].origPath; - if (p) { + const originalPath = transformedOutput?.[0].origPath || transformedOutput?.[1]; + if (originalPath) { // Resolve imports relative to original source path - return ctx.resolve(id, p, { skipSelf: true }); + return ctx.resolve(id, originalPath, { skipSelf: true }); } return; } diff --git a/starters/features/bootstrap/src/routes/bootstrap/spinners/index.tsx b/starters/features/bootstrap/src/routes/bootstrap/spinners/index.tsx index ef14c70c2dd..4b273b16589 100644 --- a/starters/features/bootstrap/src/routes/bootstrap/spinners/index.tsx +++ b/starters/features/bootstrap/src/routes/bootstrap/spinners/index.tsx @@ -14,6 +14,7 @@ export default component$(() => {